Project Structure Guide
Introduction
This page outlines common approaches to organizing software projects, especially when applying Domain-Driven Design (DDD) principles. It explains the purpose of each type of component and where it belongs in the folder hierarchy.
High-Level Organization
Domain: Core business logic and rules.
Application: Orchestration, DTOs, and use cases.
Infrastructure: Technical details like persistence, middleware, and external services.
Presentation: API endpoints, UI, or CLI.
Domain Layer
Entities: Objects defined by identity (e.g., Order, Customer).
Value Objects: Immutable types defined by attributes (e.g., Money, EmailAddress).
Aggregates: Clusters of entities and value objects with a root entity. An aggregate is a consistency boundary that ensures all changes within it are atomic and consistent. The root entity controls access and lifecycle of the aggregate.
An aggregate is a group of related objects in a domain model that are treated as a single unit to keep data consistent. It has a root entity called the aggregate root, which controls all changes inside the aggregate to ensure everything stays correct and synchronized. This means when you change something in the aggregate, you do it through the root entity so the whole group remains consistent.
For example, imagine an Order aggregate:
The Order is the aggregate root.
It contains multiple OrderLine entities representing items.
It may include value objects like Address or Money for shipping and pricing.
All changes to the order and its lines go through the Order root to keep the whole order consistent.
Domain Services: These are stateless services that encapsulate domain logic which doesn't naturally fit within an entity or value object. They represent operations or business rules that involve multiple entities or aggregates.
Implementation vs Interface: Domain Services are typically defined as interfaces in the domain layer to express the contract and domain intent. They represent the domain's business operations without technical details. The concrete implementations of these interfaces usually reside in the infrastructure or application layers, where technical concerns like persistence, external system calls, or messaging are handled.
Example: An interface like IPaymentProcessingService might be defined in the domain to represent payment logic, while the actual implementation that interacts with a payment gateway lives elsewhere.
Application Layer
DTOs (Data Transfer Objects): Simple carriers of data between layers.
Use Cases / Application Services: Coordinate domain logic for specific scenarios.
Infrastructure Layer
Repositories: Abstract persistence and mimic collections of aggregates.
Factories: Encapsulate complex creation logic for aggregates/entities.
Middleware: Handle cross-cutting concerns like caching, logging, or authentication.
External Integrations: Clients for APIs, messaging systems, or databases.
Presentation Layer
Minimal APIs / Controllers: Define endpoints and map requests to application services.
UI Components: Web pages, views, or CLI commands.
Suggested Folder Layout
You can see an example in my repository. Where to put stuff is pretty important to me as it is the key to be able to repeat the patterns and keep maintainability.
ProjectRoot/ Domain/ Entities/ ValueObjects/ Aggregates/ Services/ Application/ DTOs/ Services/ Infrastructure/ Repositories/ Factories/ Middleware/ Integrations/ Presentation/ Api/ Ui/
Key Principles
Organize by concept, not by technical detail.
Keep domain logic pure and free from infrastructure concerns.
Use consistent naming (plural for collections, singular for value objects).
Avoid catch-all folders like Utils or Common.
This structure helps ensure clarity, maintainability, and discoverability for collaborators and future maintainers.
Explanation of Aggregates
An aggregate is a consistency boundary that ensures all changes within it are atomic and consistent. The root entity controls access and lifecycle of the aggregate.
Think of an aggregate as a cluster of related objects that are treated as a single unit. The aggregate root is the main entity that manages all changes to the objects inside the aggregate.
For example, consider an Order aggregate:
The Order is the aggregate root.
It contains multiple OrderLine entities representing items.
It may include value objects like Address or Money for shipping and pricing.
All changes to the order and its lines go through the Order root to keep the whole order consistent.
Explanation of Domain Services
Domain Services are typically defined as interfaces in the domain layer. They represent stateless operations or business logic that don't naturally belong to any single entity or value object.
The actual implementations of these interfaces usually reside outside the domain layer—in the infrastructure or application layers—where technical details like persistence or external system calls are handled.
This means Domain Services define the contract and domain intent, while the concrete behavior is implemented elsewhere.
For example, an interface like IPaymentProcessingService might be defined in the domain to represent payment logic, while the actual implementation that interacts with a payment gateway lives in the infrastructure layer.
This separation helps keep the domain model clean and focused on business rules, while technical concerns are handled separately.
Summary
This page provides a clear guide to organizing a software project using Domain-Driven Design principles, including explanations of aggregates and domain services, their roles, and how to structure folders accordingly.