Conventions for implementing hexagonal (ports and adapters) architecture, keeping the domain core independent of frameworks, databases, and external services.
# Hexagonal Architecture (Ports & Adapters) Rules
## Layer Structure
- Organize code into three layers: **Domain** (core business logic), **Application** (use cases/orchestration), **Infrastructure** (adapters, frameworks, DB).
- Domain layer must have zero dependencies on frameworks, ORMs, HTTP libraries, or infrastructure packages.
- Application layer depends only on Domain. It defines ports (interfaces) and orchestrates use cases.
- Infrastructure layer depends on Application and Domain — it implements ports and wires adapters.
- Dependency rule: outer layers depend on inner layers. Inner layers never import from outer layers.
## Ports
- **Primary (driving) ports**: interfaces that expose application use cases to the outside world (HTTP, CLI, gRPC).
- **Secondary (driven) ports**: interfaces defined in Application that the domain/app requires from the outside (repositories, email, payment gateway).
- Name ports as interfaces with clear intent: `UserRepository`, `EmailSender`, `PaymentGateway`, `OrderService`.
- Define ports in the application or domain layer — never in infrastructure.
- Each port should have a single, well-defined responsibility.
## Adapters
- **Primary adapters**: translate external input (HTTP request, CLI args, message) into use case calls.
- **Secondary adapters**: implement driven ports (e.g., `PostgresUserRepository` implements `UserRepository`).
- Adapters live exclusively in the infrastructure layer.
- Name adapters by technology: `PostgresOrderRepository`, `SendgridEmailSender`, `StripePaymentGateway`.
- Adapters must not contain business logic — they only translate and delegate.
## Domain Rules
- Domain entities and value objects must be plain objects — no ORM annotations, no framework decorators.
- Business invariants are enforced inside domain entities/aggregates — not in application services.
- Domain services handle logic that spans multiple entities but has no natural home in one aggregate.
- Value objects are immutable. Entities have identity and a lifecycle.
- Never let a domain object reference a repository or any infrastructure interface.
## Application Layer
- Use case / application service methods represent a single user or system action.
- Application services orchestrate: validate input, call domain, call secondary ports, return output DTO.
- Use DTOs for input/output of use cases — never expose domain objects at application layer boundaries.
- Application services must not contain business logic — delegate to domain entities and services.
## Dependency Injection
- Wire adapters to ports via DI at the composition root (main module / bootstrap file).
- Use constructor injection everywhere — avoid service locators.
- Register adapters against their port interfaces — callers depend on abstractions, not concretions.
## Testing Strategy
- Test domain logic with pure unit tests — no mocks, no infrastructure needed.
- Test application services with mocked secondary ports (in-memory implementations).
- Test adapters with integration tests against real infrastructure (DB, APIs).
- Use in-memory adapter implementations as test doubles for all driven ports.
## Anti-Patterns
- Do not import Express, Fastify, or any HTTP framework in the domain or application layer.
- Do not use ORM models as domain entities.
- Do not bypass ports — infrastructure must never be called directly from domain.
- Do not put SQL queries or HTTP calls inside application services.
- Do not create "fat" adapters that contain business decisions.