Clean Architecture is a software design philosophy that promotes separation of concerns, allowing for more maintainable, testable, and scalable code. It is structured around organizing code into distinct layers, each with clear responsibilities and dependencies.
Independence:
Separation of Concerns:
Dependency Inversion:
Entities:
Use Cases:
Interface Adapters:
Frameworks and Drivers:
The Dependency Rule states that source code dependencies can only point inward. This means that:
Here's a visual representation of Clean Architecture:
go+---------------------------------------------+
| Frameworks and Drivers |
+---------------------------------------------+
| Interface Adapters |
+---------------------------------------------+
| Use Cases |
+---------------------------------------------+
| Entities |
+---------------------------------------------+
Here's a simple example to illustrate Clean Architecture in a Go project for a simple blog application.
Define the core business entities.
gopackage entities
type Post struct {
ID int
Title string
Content string
}
Define the application-specific business rules.
gopackage usecases
import "myapp/entities"
type PostRepository interface {
GetByID(id int) (*entities.Post, error)
Save(post *entities.Post) error
}
type PostUseCase struct {
Repo PostRepository
}
func (uc *PostUseCase) GetPost(id int) (*entities.Post, error) {
return uc.Repo.GetByID(id)
}
func (uc *PostUseCase) CreatePost(post *entities.Post) error {
return uc.Repo.Save(post)
}
Implement the adapters to convert data for the use cases and entities.
gopackage adapters
import (
"database/sql"
"myapp/entities"
"myapp/usecases"
)
type PostGORMRepo struct {
DB *sql.DB
}
func (repo *PostGORMRepo) GetByID(id int) (*entities.Post, error) {
// Code to get a post by ID from the database
}
func (repo *PostGORMRepo) Save(post *entities.Post) error {
// Code to save a post to the database
}
Define controllers to handle HTTP requests.
gopackage controllers
import (
"encoding/json"
"myapp/usecases"
"net/http"
"strconv"
)
type PostController struct {
UseCase *usecases.PostUseCase
}
func (pc *PostController) GetPost(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(r.URL.Query().Get("id"))
if err != nil {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
post, err := pc.UseCase.GetPost(id)
if err != nil {
http.Error(w, "Post not found", http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(post)
}
func (pc *PostController) CreatePost(w http.ResponseWriter, r *http.Request) {
var post entities.Post
if err := json.NewDecoder(r.Body).Decode(&post); err != nil {
http.Error(w, "Invalid input", http.StatusBadRequest)
return
}
if err := pc.UseCase.CreatePost(&post); err != nil {
http.Error(w, "Could not create post", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
}
Finally, set up the main application to wire everything together.
gopackage main
import (
"database/sql"
"myapp/adapters"
"myapp/controllers"
"myapp/usecases"
"net/http"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/myapp")
if err != nil {
panic(err)
}
postRepo := &adapters.PostGORMRepo{DB: db}
postUseCase := &usecases.PostUseCase{Repo: postRepo}
postController := &controllers.PostController{UseCase: postUseCase}
http.HandleFunc("/post", postController.GetPost)
http.HandleFunc("/create", postController.CreatePost)
http.ListenAndServe(":8080", nil)
}
Clean Architecture is a powerful and flexible approach to designing software that promotes separation of concerns and independence of frameworks, UI, databases, and other external systems. By structuring your application into entities, use cases, interface adapters, and frameworks, you can create systems that are more maintainable, scalable, and testable.