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
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
type OrderRepository interface {
Save(ctx context.Context, order *Order) error
FindByID(ctx context.Context, id OrderID) (*Order, error)
}
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)
type PostgresOrderRepository struct {
db *sql.DB
}
func (r *PostgresOrderRepository) Save(ctx context.Context, order *Order) error {
model := toOrderModel(order)
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
}
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
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