Accepted
In Clean Architecture, repository interfaces (ports) can be placed in either the domain layer (packages/core) or the application layer (packages/application). Both are valid interpretations of the pattern.
In this monorepo, we needed to decide where to define contracts like IProjectRepository, IExperienceRepository, IProfileRepository, and ISkillRepository.
The key constraint: packages/infra (which implements the repositories) must depend on the layer that defines the interfaces. Placing the interfaces in packages/application would require infra to depend on application — which is valid in some architectures but creates a tighter coupling than necessary here.
Repository interfaces are defined in packages/core alongside their respective aggregate roots, not in packages/application.
Structure:
packages/core/src/portfolio/entities/project/repositories/IProjectRepository.ts
packages/core/src/portfolio/entities/experience/repositories/IExperienceRepository.ts
packages/core/src/portfolio/entities/profile/repositories/IProfileRepository.ts
packages/core/src/portfolio/entities/skill/repositories/ISkillRepository.ts
The pattern is: repository interface lives next to its aggregate root.
Dependency flow:
core (defines interface) ← application (uses interface) ← infra (implements interface)
Positive:
- High cohesion — the interface and its aggregate root are co-located
- The domain defines its own persistence contract (true DDD)
packages/applicationdoes not need to define infrastructure contracts- Easier discoverability: to understand what operations are available for an entity, look in its own directory
packages/infraonly needs to depend onpackages/core, not onpackages/application
Negative:
- Differs from some popular Clean Architecture implementations that place all ports in
application - Requires this ADR to document the intentional deviation
-
Application Layer Interfaces — place in
packages/application/ports/repositories/:- Rejected: separates the interface from its entity, reducing cohesion; forces
infrato depend onapplication
- Rejected: separates the interface from its entity, reducing cohesion; forces
-
Separate Ports Package — create
packages/ports:- Rejected: over-engineering for this monorepo size; adds an extra package with no clear benefit over co-location with entities