# Clean Architecture in Go ## When to Use Apply clean architecture when you need strong separation between business logic and infrastructure — typically medium-to-large services (2K+ lines) where testability, framework independence, and clear dependency direction matter. Do NOT use for small CLI tools or scripts. ## The Dependency Rule Dependencies point inward only. Inner layers never import outer layers. ``` Frameworks & Drivers → Interface Adapters → Use Cases → Entities (HTTP, DB, gRPC) (handlers, repos) (app logic) (domain) ``` Each layer defines interfaces for what it needs. Outer layers implement those interfaces. ## Project Structure ``` order-service/ ├── cmd/ │ └── server/ │ └── main.go # Wiring only — builds the dependency graph ├── internal/ │ ├── entity/ │ │ ├── order.go # Order entity + business rules │ │ ├── item.go # OrderItem │ │ └── status.go # OrderStatus enum │ ├── order/ │ │ ├── place.go # PlaceOrderUseCase │ │ ├── cancel.go # CancelOrderUseCase │ │ └── port.go # Interfaces this use case depends on │ ├── adapter/ │ │ ├── handler/ │ │ │ └── order_handler.go # HTTP handler — calls use cases │ │ ├── repository/ │ │ │ └── order_postgres.go # OrderRepository — implements port │ │ └── gateway/ │ │ └── payment_client.go # External payment API client │ └── infrastructure/ │ ├── router.go # HTTP router setup │ ├── database.go # DB connection │ └── config.go # Config loading ├── go.mod └── go.sum ``` ## Code Examples ### Entity — pure domain logic, zero dependencies ```go // internal/entity/order.go package entity type Order struct { ID string Items []Item Status OrderStatus } func (o *Order) Cancel() error { if o.Status == StatusShipped { return ErrCannotCancelShipped } o.Status = StatusCancelled return nil } func (o *Order) Total() int64 { var sum int64 for _, item := range o.Items { sum += item.Price * int64(item.Quantity) } return sum } ``` ### Use Case — orchestrates business operations ```go // internal/order/port.go package order // Ports — interfaces defined by the use case, implemented by adapters type OrderRepository interface { Save(ctx context.Context, order *entity.Order) error FindByID(ctx context.Context, id string) (*entity.Order, error) } type PaymentGateway interface { Charge(ctx context.Context, orderID string, amount int64) error } ``` ```go // internal/order/place.go package order type PlaceOrderUseCase struct { orders OrderRepository payments PaymentGateway } func NewPlaceOrderUseCase(orders OrderRepository, payments PaymentGateway) *PlaceOrderUseCase { return &PlaceOrderUseCase{orders: orders, payments: payments} } func (uc *PlaceOrderUseCase) Execute(ctx context.Context, orderID string) error { order, err := uc.orders.FindByID(ctx, orderID) if err != nil { return fmt.Errorf("finding order: %w", err) } if err := uc.payments.Charge(ctx, order.ID, order.Total()); err != nil { return fmt.Errorf("charging payment: %w", err) } order.Status = entity.StatusPlaced return uc.orders.Save(ctx, order) } ``` ### Adapter — implements a port ```go // internal/adapter/repository/order_postgres.go package repository type OrderPostgres struct { db *sql.DB } func NewOrderPostgres(db *sql.DB) *OrderPostgres { return &OrderPostgres{db: db} } func (r *OrderPostgres) FindByID(ctx context.Context, id string) (*entity.Order, error) { // SQL query, scan into entity.Order } func (r *OrderPostgres) Save(ctx context.Context, order *entity.Order) error { // SQL upsert } ``` ### Handler — translates HTTP to use case calls ```go // internal/adapter/handler/order_handler.go package handler type OrderHandler struct { placeOrder *usecase.PlaceOrderUseCase } func (h *OrderHandler) HandlePlaceOrder(w http.ResponseWriter, r *http.Request) { orderID := chi.URLParam(r, "id") if err := h.placeOrder.Execute(r.Context(), orderID); err != nil { // Map domain errors to HTTP status codes http.Error(w, err.Error(), mapToHTTPStatus(err)) return } w.WriteHeader(http.StatusOK) } ``` ## Key Principle Interfaces live where they are consumed, not where they are implemented. The `usecase/order/port.go` file defines `OrderRepository` — the adapter in `adapter/repository/` implements it. This keeps the use case layer free from infrastructure imports. ## Wiring All dependency construction happens in `cmd/server/main.go`. → See `samber/cc-skills-golang@golang-dependency-injection` skill for DI library alternatives.