Clean Architecture / Hexagonal Architecture Rules
Note: Code examples in this document use framework-agnostic pseudocode (a mix of common OOP syntax). They are not tied to any specific language or framework.
1. Architecture Principles
Dependency Rule
- Dependencies always point inward: outer layers depend on inner layers, never the reverse
- The domain layer is the center and has zero external dependencies
- Framework, database, and UI are implementation details that belong to the outermost layers
- Changes in infrastructure must never require changes in domain logic
Separation of Concerns
| Layer |
Responsibility |
Changes When |
| Domain |
Business rules, entities, value objects |
Business requirements change |
| Application |
Use case orchestration, ports |
Workflow or use case changes |
| Infrastructure |
Database, messaging, external APIs |
Technology or vendor changes |
| Presentation |
HTTP, CLI, event listener adapters |
Interface or protocol changes |
Core Principles
- Business rules are independent of frameworks, databases, and delivery mechanisms
- Each layer has a well-defined boundary with explicit contracts (interfaces)
- Inner layers define interfaces (ports) that outer layers implement (adapters)
- The architecture makes the system testable without external dependencies
2. Layer Structure
Layer Hierarchy (Inside-Out)
┌─────────────────────────────────────────────┐
│ Presentation Layer │ Controllers, CLI, Event Listeners
├─────────────────────────────────────────────┤
│ Infrastructure Layer │ DB, External API, Messaging
├─────────────────────────────────────────────┤
│ Application Layer │ Use Cases, Application Services
├─────────────────────────────────────────────┤
│ Domain Layer │ Entities, Value Objects, Domain Services
└─────────────────────────────────────────────┘
▲ Dependencies point inward ▲
Layer Responsibilities
Domain Layer (Innermost)
- Entities with identity and lifecycle
- Value objects (immutable, equality by value)
- Domain services (cross-aggregate logic)
- Domain events
- Repository interfaces (outbound ports)
- No framework annotations, no infrastructure imports
Application Layer
- Use case classes / application services
- Inbound port interfaces (what the system can do)
- Outbound port interfaces (what the system needs)
- Command and query objects
- Transaction boundary management
- Event publishing orchestration
Infrastructure Layer
- Repository implementations (ORM adapters, query builders, raw SQL)
- External API clients
- Message broker producers/consumers
- File system access
- Cache implementations
- Framework-specific configuration
Presentation Layer
- REST controllers / GraphQL resolvers
- Request/response DTOs
- Input validation (format-level, not business-level)
- Authentication filter integration
- API documentation annotations
3. Ports and Adapters Pattern
See references/ports-and-adapters.md for detailed patterns including:
- Inbound ports (use cases) and inbound adapters (controllers)
- Outbound ports (repository interfaces) and outbound adapters (implementations)
4. Package Structure
Recommended Layout
order/
├── domain/ # Domain layer
│ ├── model/
│ │ ├── Order # Aggregate root
│ │ ├── OrderLine # Entity within aggregate
│ │ ├── OrderId # Value object (ID)
│ │ ├── OrderStatus # Enum
│ │ └── Money # Value object
│ ├── event/
│ │ ├── DomainEvent # Event marker interface
│ │ └── OrderConfirmedEvent # Domain event
│ ├── service/
│ │ └── OrderPricingService # Domain service
│ └── repository/
│ └── OrderRepository # Outbound port (interface)
│
├── application/ # Application layer
│ ├── port/
│ │ ├── inbound/
│ │ │ ├── CreateOrderUseCase
│ │ │ └── GetOrderQuery
│ │ └── outbound/
│ │ ├── PaymentGateway
│ │ └── NotificationSender
│ ├── service/
│ │ ├── CreateOrderService # Use case implementation
│ │ └── OrderQueryService # Query implementation
│ └── dto/
│ ├── CreateOrderCommand # Input command
│ └── OrderDetailResult # Output result
│
├── infrastructure/ # Infrastructure layer
│ ├── persistence/
│ │ ├── entity/
│ │ │ └── OrderPersistenceEntity # ORM / persistence entity
│ │ ├── repository/
│ │ │ └── OrderRepositoryImpl # Outbound adapter
│ │ └── mapper/
│ │ └── OrderEntityMapper # Persistence ↔ Domain mapper
│ ├── external/
│ │ └── StripePaymentGateway # External API adapter
│ ├── messaging/
│ │ └── KafkaNotificationSender # Messaging adapter
│ └── config/
│ └── PersistenceConfig # Infrastructure config
│
└── presentation/ # Presentation layer
├── controller/
│ └── OrderController # REST inbound adapter
├── dto/
│ ├── CreateOrderRequest # API request DTO
│ └── OrderDetailResponse # API response DTO
└── mapper/
└── OrderResponseMapper # Request/Response ↔ Command/Result
Package Dependency Rules
presentation → application (invokes use cases)
infrastructure → domain (implements repository ports)
infrastructure → application (implements outbound ports)
application → domain (uses domain model)
domain → (nothing) (no outward dependencies)
domain package must not import from application, infrastructure, or presentation
application package must not import from infrastructure or presentation
presentation must not import from infrastructure directly
- Cross-cutting via dependency injection only (DI framework wires adapters to ports)
5. Data Transformation Between Layers
See references/data-transformation.md for detailed patterns including mapping examples, use case implementation, and test examples.
Key Rules
- Each layer boundary has its own data objects -- never pass persistence entities to controllers
- Mapping logic lives at the boundary of the outer layer (adapter side)
- Domain objects never depend on DTO or persistence entity classes
- Use dedicated mapper classes or mapping functions for conversions
6. Dependency Inversion Principle (DIP)
Core Mechanism
The domain and application layers define interfaces (ports) that the infrastructure layer implements. A dependency injection framework wires the concrete implementations at runtime.
// Domain layer defines the interface
interface OrderRepository {
fun findById(id: OrderId): Order?
fun save(order: Order)
}
// Infrastructure layer implements it
// Repository implementation (infrastructure layer)
class OrderRepositoryImpl implements OrderRepository {
private persistenceRepository: OrderPersistenceRepository
fun findById(id: OrderId): Order? { ... }
fun save(order: Order) { ... }
}
// Application layer depends only on the interface
// Application service
class CreateOrderService implements CreateOrderUseCase {
private orderRepository: OrderRepository // Port, not adapter
private paymentGateway: PaymentGateway // Port, not adapter
fun execute(command: CreateOrderCommand): OrderId { ... }
}
DIP Benefits
| Without DIP |
With DIP |
Service depends on OrderRepositoryImpl |
Service depends on OrderRepository (port) |
| Changing DB requires changing service code |
Changing DB only requires new adapter |
| Testing requires real DB or mock framework |
Testing uses simple fake implementation |
| Domain coupled to framework |
Domain is framework-free |
DIP Application Rules
- Define interfaces in the layer that needs the capability (domain or application)
- Implement interfaces in the outer layer that provides the capability (infrastructure)
- Never create an interface just to have an interface -- use DIP only when the boundary is meaningful
- Framework annotations belong on implementations, not on port interfaces
7. Use Case / Application Service Pattern
Use Case Design Rules
- One class per use case (Single Responsibility)
- Use cases are thin orchestrators -- business logic belongs in domain objects
- Use cases handle transaction boundaries, not domain objects
- Input is a command/query object, output is a result object or domain ID
- Never return domain entities from use cases -- return result DTOs or IDs
- Use case names describe business actions, not technical operations
Command vs Query Separation (CQS)
| Aspect |
Command |
Query |
| Purpose |
Change state |
Read state |
| Return |
Void or created ID |
Result DTO |
| Side |
Write side |
Read side |
| Tx |
Read-write tx |
Read-only tx |
| Example |
CreateOrderUseCase |
GetOrderDetailQuery |
8. Testability by Design
Testing Strategy Per Layer
| Layer |
Test Type |
Dependencies |
Speed |
| Domain |
Unit test |
None (pure logic) |
Fast |
| Application |
Unit test |
Fake ports (in-memory) |
Fast |
| Infrastructure |
Integration test |
Test containers, HTTP mock servers |
Slow |
| Presentation |
API test |
HTTP test client, mock services |
Medium |
Testability Rules
- Domain layer tests require zero mocking -- if mocking is needed, the domain has external dependencies (violation)
- Application layer tests use fake implementations of ports, not mocks
- Infrastructure tests verify that adapters correctly translate between domain and technology
- Presentation tests verify HTTP contract (status codes, response structure), not business logic
- If a class is hard to test, it likely violates separation of concerns -- fix the design, not the test
9. Anti-Patterns
Domain Layer Violations
- Framework annotations in domain: ORM annotations, DI annotations, transaction annotations on domain classes couples domain to framework
- Infrastructure imports in domain: Domain classes importing framework or infrastructure packages
- Anemic domain model: Domain objects with only getters/setters and all logic in services
- Domain returning infrastructure types: Domain methods returning paginated wrappers, HTTP response objects, or persistence entities
Dependency Violations
- Bidirectional dependencies: Application layer depending on infrastructure and infrastructure depending back on application
- Skipping layers: Controller directly calling repository without going through use case
- Shared mutable state: Passing persistence entities across layer boundaries (lazy loading failures, unintended mutations)
Structural Violations
- God use case: Single application service handling dozens of unrelated operations
- Leaky abstraction: Outbound port method signatures exposing infrastructure details (e.g.,
fun findByRawQuery(query: String))
- DTO explosion: Creating separate DTOs for every minor variation instead of reusing where appropriate
- Premature abstraction: Creating ports and adapters for internal modules that will never have multiple implementations
Common Mistakes
| Mistake |
Why It Hurts |
Fix |
| Persistence entity as domain entity |
Domain coupled to persistence framework |
Separate domain model and persistence entity |
| Business logic in controller |
Untestable without HTTP context |
Move to domain or application layer |
| Repository returning DTOs |
Mixes persistence and presentation |
Return domain objects, map at boundary |
| Transaction annotations on domain service |
Domain depends on framework |
Put transaction management on application service |
| Using framework events as domain events |
Domain coupled to framework event system |
Domain defines events, application publishes via framework |
10. Related Rules
| Related Skill |
When to Reference |
ddd skill |
Designing entities, aggregates, value objects, domain events |
code-quality skill |
Abstraction layers, modularity, single responsibility |
testing-unit skill |
Writing tests for use cases and domain logic |
error-handling skill |
Exception hierarchy, business vs system exceptions |
spring-framework skill |
Spring DI wiring, @Transactional, JPA repository patterns |
Additional Resources
- Alistair Cockburn, "Hexagonal Architecture" (original article, 2005)
- Robert C. Martin, "Clean Architecture" concepts and dependency rule
- Vaughn Vernon, "Implementing Domain-Driven Design" (architecture patterns chapter)
- Netflix Tech Blog, "Ready for changes with Hexagonal Architecture"
- Herberto Graca, "DDD, Hexagonal, Onion, Clean, CQRS, How I put it all together" (blog series)
- Tom Hombergs, "Get Your Hands Dirty on Clean Architecture"
1---2name: clean-architecture3description: Clean Architecture and Hexagonal Architecture (Ports & Adapters) patterns. Covers the dependency rule, domain layer isolation, use case (application service) design, repository pattern, input/output port definitions, adapter implementation, and onion architecture layering. Use when designing layered architecture, defining port/adapter boundaries, structuring domain-centric applications, or enforcing the dependency rule between infrastructure and domain layers.4license: MIT5---67# Clean Architecture / Hexagonal Architecture Rules89> **Note**: Code examples in this document use framework-agnostic pseudocode (a mix of common OOP syntax). They are not tied to any specific language or framework.1011## 1. Architecture Principles1213### Dependency Rule1415- Dependencies always point inward: outer layers depend on inner layers, never the reverse16- The domain layer is the center and has zero external dependencies17- Framework, database, and UI are implementation details that belong to the outermost layers18- Changes in infrastructure must never require changes in domain logic1920### Separation of Concerns2122| Layer | Responsibility | Changes When |23| -------------- | --------------------------------------- | ----------------------------- |24| Domain | Business rules, entities, value objects | Business requirements change |25| Application | Use case orchestration, ports | Workflow or use case changes |26| Infrastructure | Database, messaging, external APIs | Technology or vendor changes |27| Presentation | HTTP, CLI, event listener adapters | Interface or protocol changes |2829### Core Principles3031- Business rules are independent of frameworks, databases, and delivery mechanisms32- Each layer has a well-defined boundary with explicit contracts (interfaces)33- Inner layers define interfaces (ports) that outer layers implement (adapters)34- The architecture makes the system testable without external dependencies3536---3738## 2. Layer Structure3940### Layer Hierarchy (Inside-Out)4142```text43┌─────────────────────────────────────────────┐44│ Presentation Layer │ Controllers, CLI, Event Listeners45├─────────────────────────────────────────────┤46│ Infrastructure Layer │ DB, External API, Messaging47├─────────────────────────────────────────────┤48│ Application Layer │ Use Cases, Application Services49├─────────────────────────────────────────────┤50│ Domain Layer │ Entities, Value Objects, Domain Services51└─────────────────────────────────────────────┘52 ▲ Dependencies point inward ▲53```5455### Layer Responsibilities5657#### Domain Layer (Innermost)5859- Entities with identity and lifecycle60- Value objects (immutable, equality by value)61- Domain services (cross-aggregate logic)62- Domain events63- Repository interfaces (outbound ports)64- No framework annotations, no infrastructure imports6566#### Application Layer6768- Use case classes / application services69- Inbound port interfaces (what the system can do)70- Outbound port interfaces (what the system needs)71- Command and query objects72- Transaction boundary management73- Event publishing orchestration7475#### Infrastructure Layer7677- Repository implementations (ORM adapters, query builders, raw SQL)78- External API clients79- Message broker producers/consumers80- File system access81- Cache implementations82- Framework-specific configuration8384#### Presentation Layer8586- REST controllers / GraphQL resolvers87- Request/response DTOs88- Input validation (format-level, not business-level)89- Authentication filter integration90- API documentation annotations9192---9394## 3. Ports and Adapters Pattern9596> **See [references/ports-and-adapters.md](references/ports-and-adapters.md) for detailed patterns including:**97>98> - Inbound ports (use cases) and inbound adapters (controllers)99> - Outbound ports (repository interfaces) and outbound adapters (implementations)100101---102103## 4. Package Structure104105### Recommended Layout106107```text108order/109├── domain/ # Domain layer110│ ├── model/111│ │ ├── Order # Aggregate root112│ │ ├── OrderLine # Entity within aggregate113│ │ ├── OrderId # Value object (ID)114│ │ ├── OrderStatus # Enum115│ │ └── Money # Value object116│ ├── event/117│ │ ├── DomainEvent # Event marker interface118│ │ └── OrderConfirmedEvent # Domain event119│ ├── service/120│ │ └── OrderPricingService # Domain service121│ └── repository/122│ └── OrderRepository # Outbound port (interface)123│124├── application/ # Application layer125│ ├── port/126│ │ ├── inbound/127│ │ │ ├── CreateOrderUseCase128│ │ │ └── GetOrderQuery129│ │ └── outbound/130│ │ ├── PaymentGateway131│ │ └── NotificationSender132│ ├── service/133│ │ ├── CreateOrderService # Use case implementation134│ │ └── OrderQueryService # Query implementation135│ └── dto/136│ ├── CreateOrderCommand # Input command137│ └── OrderDetailResult # Output result138│139├── infrastructure/ # Infrastructure layer140│ ├── persistence/141│ │ ├── entity/142│ │ │ └── OrderPersistenceEntity # ORM / persistence entity143│ │ ├── repository/144│ │ │ └── OrderRepositoryImpl # Outbound adapter145│ │ └── mapper/146│ │ └── OrderEntityMapper # Persistence ↔ Domain mapper147│ ├── external/148│ │ └── StripePaymentGateway # External API adapter149│ ├── messaging/150│ │ └── KafkaNotificationSender # Messaging adapter151│ └── config/152│ └── PersistenceConfig # Infrastructure config153│154└── presentation/ # Presentation layer155 ├── controller/156 │ └── OrderController # REST inbound adapter157 ├── dto/158 │ ├── CreateOrderRequest # API request DTO159 │ └── OrderDetailResponse # API response DTO160 └── mapper/161 └── OrderResponseMapper # Request/Response ↔ Command/Result162```163164### Package Dependency Rules165166```text167presentation → application (invokes use cases)168infrastructure → domain (implements repository ports)169infrastructure → application (implements outbound ports)170application → domain (uses domain model)171domain → (nothing) (no outward dependencies)172```173174- `domain` package must not import from `application`, `infrastructure`, or `presentation`175- `application` package must not import from `infrastructure` or `presentation`176- `presentation` must not import from `infrastructure` directly177- Cross-cutting via dependency injection only (DI framework wires adapters to ports)178179---180181## 5. Data Transformation Between Layers182183> See [references/data-transformation.md](references/data-transformation.md) for detailed patterns including mapping examples, use case implementation, and test examples.184185### Key Rules186187- Each layer boundary has its own data objects -- never pass persistence entities to controllers188- Mapping logic lives at the boundary of the outer layer (adapter side)189- Domain objects never depend on DTO or persistence entity classes190- Use dedicated mapper classes or mapping functions for conversions191192---193194## 6. Dependency Inversion Principle (DIP)195196### Core Mechanism197198The domain and application layers define interfaces (ports) that the infrastructure layer implements. A dependency injection framework wires the concrete implementations at runtime.199200```text201// Domain layer defines the interface202interface OrderRepository {203 fun findById(id: OrderId): Order?204 fun save(order: Order)205}206207// Infrastructure layer implements it208// Repository implementation (infrastructure layer)209class OrderRepositoryImpl implements OrderRepository {210 private persistenceRepository: OrderPersistenceRepository211212 fun findById(id: OrderId): Order? { ... }213 fun save(order: Order) { ... }214}215216// Application layer depends only on the interface217// Application service218class CreateOrderService implements CreateOrderUseCase {219 private orderRepository: OrderRepository // Port, not adapter220 private paymentGateway: PaymentGateway // Port, not adapter221222 fun execute(command: CreateOrderCommand): OrderId { ... }223}224```225226### DIP Benefits227228| Without DIP | With DIP |229| ------------------------------------------------ | ------------------------------------------- |230| Service depends on `OrderRepositoryImpl` | Service depends on `OrderRepository` (port) |231| Changing DB requires changing service code | Changing DB only requires new adapter |232| Testing requires real DB or mock framework | Testing uses simple fake implementation |233| Domain coupled to framework | Domain is framework-free |234235### DIP Application Rules236237- Define interfaces in the layer that needs the capability (domain or application)238- Implement interfaces in the outer layer that provides the capability (infrastructure)239- Never create an interface just to have an interface -- use DIP only when the boundary is meaningful240- Framework annotations belong on implementations, not on port interfaces241242---243244## 7. Use Case / Application Service Pattern245246### Use Case Design Rules247248- One class per use case (Single Responsibility)249- Use cases are thin orchestrators -- business logic belongs in domain objects250- Use cases handle transaction boundaries, not domain objects251- Input is a command/query object, output is a result object or domain ID252- Never return domain entities from use cases -- return result DTOs or IDs253- Use case names describe business actions, not technical operations254255### Command vs Query Separation (CQS)256257| Aspect | Command | Query |258| ------- | -------------------- | -------------------------- |259| Purpose | Change state | Read state |260| Return | Void or created ID | Result DTO |261| Side | Write side | Read side |262| Tx | Read-write tx | Read-only tx |263| Example | `CreateOrderUseCase` | `GetOrderDetailQuery` |264265---266267## 8. Testability by Design268269### Testing Strategy Per Layer270271| Layer | Test Type | Dependencies | Speed |272| -------------- | ---------------- | ---------------------------------- | ------ |273| Domain | Unit test | None (pure logic) | Fast |274| Application | Unit test | Fake ports (in-memory) | Fast |275| Infrastructure | Integration test | Test containers, HTTP mock servers | Slow |276| Presentation | API test | HTTP test client, mock services | Medium |277278### Testability Rules279280- Domain layer tests require zero mocking -- if mocking is needed, the domain has external dependencies (violation)281- Application layer tests use fake implementations of ports, not mocks282- Infrastructure tests verify that adapters correctly translate between domain and technology283- Presentation tests verify HTTP contract (status codes, response structure), not business logic284- If a class is hard to test, it likely violates separation of concerns -- fix the design, not the test285286---287288## 9. Anti-Patterns289290### Domain Layer Violations291292- **Framework annotations in domain**: ORM annotations, DI annotations, transaction annotations on domain classes couples domain to framework293- **Infrastructure imports in domain**: Domain classes importing framework or infrastructure packages294- **Anemic domain model**: Domain objects with only getters/setters and all logic in services295- **Domain returning infrastructure types**: Domain methods returning paginated wrappers, HTTP response objects, or persistence entities296297### Dependency Violations298299- **Bidirectional dependencies**: Application layer depending on infrastructure and infrastructure depending back on application300- **Skipping layers**: Controller directly calling repository without going through use case301- **Shared mutable state**: Passing persistence entities across layer boundaries (lazy loading failures, unintended mutations)302303### Structural Violations304305- **God use case**: Single application service handling dozens of unrelated operations306- **Leaky abstraction**: Outbound port method signatures exposing infrastructure details (e.g., `fun findByRawQuery(query: String)`)307- **DTO explosion**: Creating separate DTOs for every minor variation instead of reusing where appropriate308- **Premature abstraction**: Creating ports and adapters for internal modules that will never have multiple implementations309310### Common Mistakes311312| Mistake | Why It Hurts | Fix |313| ----------------------------------------- | ---------------------------------------- | ---------------------------------------------------------- |314| Persistence entity as domain entity | Domain coupled to persistence framework | Separate domain model and persistence entity |315| Business logic in controller | Untestable without HTTP context | Move to domain or application layer |316| Repository returning DTOs | Mixes persistence and presentation | Return domain objects, map at boundary |317| Transaction annotations on domain service | Domain depends on framework | Put transaction management on application service |318| Using framework events as domain events | Domain coupled to framework event system | Domain defines events, application publishes via framework |319320---321322## 10. Related Rules323324| Related Skill | When to Reference |325| ------------------------ | ------------------------------------------------------------ |326| `ddd` skill | Designing entities, aggregates, value objects, domain events |327| `code-quality` skill | Abstraction layers, modularity, single responsibility |328| `testing-unit` skill | Writing tests for use cases and domain logic |329| `error-handling` skill | Exception hierarchy, business vs system exceptions |330| `spring-framework` skill | Spring DI wiring, `@Transactional`, JPA repository patterns |331332---333334## Additional Resources335336- Alistair Cockburn, "Hexagonal Architecture" (original article, 2005)337- Robert C. Martin, "Clean Architecture" concepts and dependency rule338- Vaughn Vernon, "Implementing Domain-Driven Design" (architecture patterns chapter)339- Netflix Tech Blog, "Ready for changes with Hexagonal Architecture"340- Herberto Graca, "DDD, Hexagonal, Onion, Clean, CQRS, How I put it all together" (blog series)341- Tom Hombergs, "Get Your Hands Dirty on Clean Architecture"