This document is the canonical architectural reference for the E-commerce Store API. It defines the strict academic rules of Domain-Driven Design (DDD) and Hexagonal Architecture (Ports & Adapters) as applied to this codebase. All contributors must read and follow this document.
Companion docs:
ARCHITECTURE.md(system context & domain flows),CONTRIBUTING.md(contribution guidelines)
Source: Alistair Cockburn, 2005
The Hexagonal Architecture organizes software as a core application surrounded by ports and adapters. The application does not know how it is driven or what external systems it talks to.
┌──────────────────────────────────────────────────────────────┐
│ PRIMARY ADAPTERS │
│ (Driving Side — "Who triggers us") │
│ Controllers · CLI · Cron · WebSocket Gateways · Consumers │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ APPLICATION CORE │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ DOMAIN LAYER │ │ │
│ │ │ Entities · Value Objects · Domain Services │ │ │
│ │ │ Repository Interfaces · Domain Events │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ APPLICATION LAYER │ │
│ │ Use Cases · Application Services · Port Interfaces │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ SECONDARY ADAPTERS │
│ (Driven Side — "What we talk to") │
│ DB Repos · API Clients · Cache · Queue · Logger Impl │
└──────────────────────────────────────────────────────────────┘
Dependencies ALWAYS point inwards.
| Layer | May depend on | Must NOT depend on |
|---|---|---|
| Domain | Nothing | Application, Adapters, Frameworks |
| Application | Domain only | Adapters, Frameworks |
| Primary Adapters | Application | Domain directly, Secondary Adapters |
| Secondary Adapters | Domain interfaces, Application ports | Primary Adapters |
| Concept | Academic Definition | This Project's Convention |
|---|---|---|
| Port | An interface defined by the application core that the outside world must conform to | Abstract classes in domain/repositories/ and application/ports/ |
| Primary Adapter | Code that drives the application (sends input) | primary-adapters/ folder — Controllers, DTOs, Job handlers, Guards |
| Secondary Adapter | Code that the application drives (sends output) | secondary-adapters/ folder — Repository impls, API clients, Cache impls |
| Driving Actor | External entity that triggers the application | HTTP client, Cron scheduler, WebSocket client |
| Driven Actor | External dependency the application uses | PostgreSQL, Redis, Stripe/PayPal Gateway |
src/modules/[module]/
├── core/
│ ├── domain/ ← THE INNERMOST RING
│ │ ├── models/ ← Entities, Aggregates
│ │ ├── value-objects/ ← Immutable value types
│ │ ├── repositories/ ← PORT: Storage abstractions
│ │ └── events/ ← Domain events
│ └── application/ ← ORCHESTRATION RING
│ ├── usecases/ ← Application-specific business rules
│ ├── services/ ← Cross-cutting application logic
│ └── ports/ ← PORT: External service abstractions
│
│
├── primary-adapters/ ← PRIMARY ADAPTERS (driving)
│ ├── dtos/ ← Input/Output transformation
│ └── jobs/ ← Background job handlers
│
└── secondary-adapters/ ← SECONDARY ADAPTERS (driven)
├── database/ ← TypeORM entities, ORM config
├── persistence/mappers/ ← Domain ↔ ORM entity translation
├── repositories/ ← Concrete repository implementations
├── gateways/ ← Concrete gateway implementations
└── schedulers/ ← Concrete scheduler implementations
Source: Eric Evans, "Domain-Driven Design: Tackling Complexity in the Heart of Software", 2003
| Pattern | Definition | This Project |
|---|---|---|
| Bounded Context | A boundary within which a domain model is defined and applicable | Each folder in src/modules/ is a Bounded Context |
| Shared Kernel | A subset of the domain model shared between multiple contexts. Must be pure domain — no infrastructure | src/shared-kernel/domain/ — contains only Result, AppError, UseCase, Money, Quantity, IdempotencyStore |
| Context Map | Documents the relationships between Bounded Contexts | Orders imports from Customers (ACL via CustomerGateway), Carts (ACL via CartGateway) |
| Upstream/Downstream | One context provides, another consumes | Orders (downstream) consumes Customers, Carts, Inventory, Payments (upstream) |
| Anti-Corruption Layer | Translates between two contexts' models | Gateway adapters in secondary-adapters/adapters/ (e.g., CustomerGatewayAdapter, CartGatewayAdapter) |
| Pattern | Definition | Location in Codebase |
|---|---|---|
| Entity | Has identity, mutable state, lifecycle | core/domain/models/ |
| Value Object | Defined by attributes, immutable, no identity | core/domain/value-objects/ |
| Aggregate | Cluster of entities with a single root entity | Domain models that encapsulate child entities |
| Repository | Abstracts storage operations for aggregates | core/domain/repositories/ (interface) → secondary-adapters/repositories/ (impl) |
| Domain Service | Business logic that doesn't belong to one entity | core/domain/services/ |
| Domain Event | Records something significant that happened | core/domain/events/ |
| Application Service / Use Case | Orchestrates domain objects for a specific task | core/application/usecases/ |
| Port | Interface the core defines for external interaction | core/application/ports/ |
| Factory | Encapsulates complex creation logic | ErrorFactory in shared-kernel |
A Shared Kernel is a contract between teams. Anything that goes into the Shared Kernel becomes a dependency for all Bounded Contexts, so it must be:
- Pure domain — No framework imports, no infrastructure, no I/O
- Minimal — Only include what genuinely has no single owner
- Stable — Changing Shared Kernel breaks all consumers
- Versioned conceptually — Any change must be treated as a breaking change
What belongs in Shared Kernel:
- Generic type primitives (
Result<T, E>) - Base error hierarchy (
AppError,ErrorFactory) - Abstract contracts (
UseCase<I, O, E>,ApplicationService<Req, Res>) - Truly universal value objects (
Money,Quantity)
What does NOT belong in Shared Kernel:
- ❌ Feature-specific domain files (even if used by many modules)
- ❌ Infrastructure (databases, caches, loggers)
- ❌ Controllers, middleware, guards
- ❌ NestJS module files
Cross-context imports ARE allowed — this is how Context Mapping works in DDD:
// ✅ CORRECT: Orders module imports from Customers' domain via ACL Gateway
import { CustomerGateway } from '../core/application/gateways/customer.gateway';
// ✅ CORRECT: Carts module imports from Inventory's domain
import { InventoryGateway } from '../core/application/gateways/inventory.gateway';
// ❌ WRONG: Importing infrastructure from another module
import { RedisCustomerRepo } from 'src/modules/customers/secondary-adapters/repositories/redis.customer-repo';Rule: You may import another module's domain contracts (entities, value objects, interfaces). You must never import another module's adapters (repositories, gateways, controllers).
This is a common source of confusion. Here is the precise distinction:
| Term | Scope | What It Contains | In This Project |
|---|---|---|---|
| Primary Adapters | Driving side | Controllers, Guards, Interceptors, Filters, CLI, Cron triggers | modules/[x]/primary-adapters/, src/interceptors/ |
| Secondary Adapters | Driven side | Repository impls, API clients, Cache impls, Queue producers | modules/[x]/secondary-adapters/ |
| Global Infrastructure | Shared driven-side | DB connections, Cache config, Redis setup, Logger, External APIs | src/infrastructure/ |
Key insight: src/infrastructure/ is the global secondary adapter layer. It is NOT a catch-all for everything. Primary adapters (filters, interceptors) live at the app level (src/filters/, src/interceptors/) because they are presentation concerns, not infrastructure.
Use this flowchart when adding new code:
Is it a pure type/interface with NO feature owner?
└─ YES → shared-kernel/domain/
Does it drive the app (receives input from the outside)?
└─ YES → Is it module-specific?
└─ YES → modules/[module]/primary-adapters/
└─ NO (global) → src/interceptors/
Does the app drive it (talks to external systems)?
└─ YES → Is it module-specific?
└─ YES → modules/[module]/secondary-adapters/
└─ NO (global) → src/infrastructure/
Does it contain business rules?
└─ YES → modules/[module]/core/domain/
Does it orchestrate domain objects?
└─ YES → modules/[module]/core/application/
Does it have its own controller + use case?
└─ YES → It's a Bounded Context → modules/[new-module]/
| Concept | Module-Level Name | Global-Level Name |
|---|---|---|
| Driving adapters | primary-adapters/ |
src/filters/, src/interceptors/ |
| Driven adapters | secondary-adapters/ |
src/infrastructure/ |
| Business core | core/domain/ + core/application/ |
shared-kernel/domain/ |
| NestJS module | [module].module.ts |
infrastructure.module.ts |
- Evans, Eric. Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley, 2003.
- Cockburn, Alistair. Hexagonal Architecture. 2005. https://alistair.cockburn.us/hexagonal-architecture/
- Vernon, Vaughn. Implementing Domain-Driven Design. Addison-Wesley, 2013.
- Millett, Scott & Tune, Nick. Patterns, Principles, and Practices of Domain-Driven Design. Wrox, 2015.