When to Use
- Designing a new project structure from scratch
- Refactoring existing code to clean architecture
- Creating domain entities, use cases, or repositories
- Defining boundaries between layers
- Implementing dependency injection patterns
Core Principles
| Principle | Description |
|---|
| Dependency Rule | Dependencies point inward. Inner layers know nothing about outer layers |
| Entities | Enterprise business rules, independent of application |
| Use Cases | Application business rules, orchestrate entities |
| Interface Adapters | Convert data between use cases and external agents |
| Frameworks & Drivers | External tools (DB, Web, UI) - outermost layer |
Layer Structure
project/
├── domain/ # Innermost - Enterprise Business Rules
│ ├── entities/ # Business objects with behavior
│ └── value_objects/ # Immutable domain primitives
│
├── application/ # Application Business Rules
│ ├── use_cases/ # Application-specific business rules
│ ├── ports/ # Interfaces (input/output boundaries)
│ │ ├── input/ # Use case interfaces (driven)
│ │ └── output/ # Repository/service interfaces (driving)
│ └── dto/ # Data Transfer Objects
│
├── adapters/ # Interface Adapters
│ ├── controllers/ # Input adapters (HTTP, CLI, gRPC)
│ ├── presenters/ # Output formatters
│ ├── repositories/ # Data persistence implementations
│ └── gateways/ # External service implementations
│
└── infrastructure/ # Frameworks & Drivers
├── config/ # Configuration loading
├── database/ # DB connections, migrations
├── http/ # HTTP server setup
└── di/ # Dependency injection container
Critical Patterns
1. Dependency Inversion
WRONG: UseCase depends on concrete Repository
RIGHT: UseCase depends on Repository interface (port)
Concrete Repository implements the interface
2. Entity Design
// Domain Entity - contains business logic
type Order struct {
ID OrderID
Items []OrderItem
Status OrderStatus
CreatedAt time.Time
}
func (o *Order) AddItem(item OrderItem) error {
if o.Status != StatusDraft {
return ErrOrderNotModifiable
}
o.Items = append(o.Items, item)
return nil
}
func (o *Order) Total() Money {
var total Money
for _, item := range o.Items {
total = total.Add(item.Subtotal())
}
return total
}
3. Use Case / Interactor
// Port (interface) - defined in application layer
type OrderRepository interface {
Save(ctx context.Context, order *Order) error
FindByID(ctx context.Context, id OrderID) (*Order, error)
}
// Use Case - orchestrates domain logic
type CreateOrderUseCase struct {
orderRepo OrderRepository
eventBus EventPublisher
}
func (uc *CreateOrderUseCase) Execute(ctx context.Context, input CreateOrderInput) (*CreateOrderOutput, error) {
order := NewOrder(input.CustomerID)
for _, item := range input.Items {
if err := order.AddItem(item); err != nil {
return nil, err
}
}
if err := uc.orderRepo.Save(ctx, order); err != nil {
return nil, err
}
uc.eventBus.Publish(OrderCreatedEvent{OrderID: order.ID})
return &CreateOrderOutput{OrderID: order.ID}, nil
}
4. Repository Implementation (Adapter)
// Adapter - implements the port
type PostgresOrderRepository struct {
db *sql.DB
}
func (r *PostgresOrderRepository) Save(ctx context.Context, order *Order) error {
// Convert domain entity to DB model
model := toOrderModel(order)
// Persist using infrastructure
return r.db.Save(ctx, model)
}
func (r *PostgresOrderRepository) FindByID(ctx context.Context, id OrderID) (*Order, error) {
model, err := r.db.FindByID(ctx, id)
if err != nil {
return nil, err
}
// Convert DB model back to domain entity
return toOrderEntity(model), nil
}
Layer Communication Rules
| From | To | Allowed? | How |
|---|
| Infrastructure | Adapters | Yes | Direct import |
| Adapters | Application | Yes | Via ports (interfaces) |
| Application | Domain | Yes | Direct import |
| Domain | Application | NO | Never |
| Application | Adapters | NO | Use dependency injection |
| Adapters | Infrastructure | Yes | Direct import |
Decision Tree: Where Does This Code Go?
Is it a business rule that exists regardless of application?
├─ YES → domain/entities/
└─ NO
Is it application-specific business logic?
├─ YES → application/use_cases/
└─ NO
Does it convert data between formats?
├─ YES → adapters/
└─ NO → infrastructure/
Anti-Patterns to Avoid
| Anti-Pattern | Problem | Solution |
|---|
| Anemic Domain | Entities with only getters/setters | Add business methods to entities |
| Leaky Abstraction | Domain knows about DB/HTTP | Use ports for external concerns |
| Use Case Bloat | Too much logic in use cases | Extract to domain entities |
| Shared DTOs | Same DTO across layers | Create layer-specific DTOs |
| Direct Infrastructure | Controller calls DB directly | Always go through use cases |
Testing Strategy
| Layer | Test Type | Dependencies |
|---|
| Domain | Unit tests | None (pure logic) |
| Application | Unit tests | Mock ports |
| Adapters | Integration tests | Real/test infrastructure |
| Infrastructure | Integration tests | Real external systems |
Commands
# Typical directory creation for new clean architecture project
mkdir -p domain/{entities,value_objects}
mkdir -p application/{use_cases,ports/{input,output},dto}
mkdir -p adapters/{controllers,repositories,gateways,presenters}
mkdir -p infrastructure/{config,database,http,di}
Resources
- Reference: Uncle Bob's Clean Architecture book
- Pattern: Hexagonal Architecture (Ports and Adapters) - related pattern
- Pattern: Onion Architecture - related pattern