Domain layer¶
The Domain layer is the core of the PetFolio Service. It contains all business logic, entities, value objects, and repository interfaces. This layer has zero external dependencies - it defines the rules that the rest of the application must follow.
Clean Architecture - innermost layer
Every other layer depends on Domain, but Domain depends on nothing. This isolation ensures that business rules are never compromised by infrastructure concerns like databases, HTTP, or external services.
Responsibility¶
- Defining entities: Core business objects with behaviour (e.g.
Animal,Account,User) - Defining value objects: Immutable types that enforce business invariants (e.g.
Email,Name,Weight,Species) - Declaring repository interfaces: Contracts that Infrastructure implements (e.g.
IAnimalRepository,IUnitOfWork) - Declaring service interfaces: Contracts for cross-cutting concerns (e.g.
ICurrentUserService,ITenantProvider,IClaimsPrincipalAccessor) - Raising domain events: For cross-aggregate communication (e.g.
AnimalCreatedEvent) - Providing error definitions: For domain-specific failures (e.g.
AnimalErrors,AccountErrors)
Components¶
graph TB
subgraph "Entities - rich domain models"
E1[Account<br>+ Create<br>+ AddUser<br>+ RemoveUser]
E2[Animal<br>+ Create<br>+ SetDateOfBirth<br>+ SetCurrentWeight]
E3[User<br>+ Create<br>+ SetEmailAddress]
end
subgraph "Value objects - immutable"
VO1[Email<br>RFC 5322 validation]
VO2[Name<br>1-100 chars]
VO3[Weight<br>Kg/Lb/G conversion]
VO4[Species]
VO5[Breed]
VO6[Age<br>Calculated from DOB]
end
subgraph "Repository interfaces"
R1[IAnimalRepository]
R2[IAccountRepository]
R3[IUserRepository]
R4[IUnitOfWork]
end
subgraph "Domain events"
DE1[AnimalCreatedEvent]
DE2[AccountCreatedEvent]
DE3[AccountUserAddedEvent]
end
subgraph "Common types"
CT1["Result<T><br>Success/Failure pattern"]
CT2[PetfolioError<br>Code + Message]
CT3[Entity Base Class]
end
E2 --> VO1
E2 --> VO2
E2 --> VO3
E3 --> VO1
E1 --> VO2
E2 --> DE1
E1 --> DE2
Entities¶
Entities are rich domain models - they contain both data and behaviour. Each entity inherits from Entity, which provides Id, CreatedAtUtc, UpdatedAtUtc, and domain event management.
DO: Encapsulate business logic and data together in entities
DON'T: Create entities with only properties and no behaviour
Rich domain model example
The following entity illustrates the rich domain model pattern used throughout the Domain layer. Real entities follow the same conventions: a static Create() factory method, value objects for type-safe fields, and behaviour methods that enforce business rules.
Key points demonstrated in the diagram:
- Private parameterless constructor for EF Core hydration
- Static
Create()factory validates arguments and raises a domain event - Behaviour methods (
Cancel,Reschedule) enforce invariants rather than exposing raw setters - Value objects (
Name) provide type safety and built-in validation - Domain events (
AppointmentCreatedEvent,AppointmentCancelledEvent) enable cross-aggregate communication
Value objects¶
Value objects are immutable types that enforce business invariants. They are always sealed record types with a static Create() factory method.
DO: Use sealed records with a static Create() factory that validates invariants
DON'T: Use primitive types directly or allow construction without validation
Value object example
classDiagram
class Email {
<<sealed record>>
+string Value
+string Normalised
+Create(string value)$ Email
-Email()
-Email(string value, string normalised)
-ValidateEmail(string email)$ void
}
note for Email "Immutable value object\n- Private parameterless constructor for EF Core\n- Create() factory validates RFC 5322\n- Max length 254 chars\n- Normalises to lowercase\n- Throws ArgumentException if invalid"
classDef value fill:#c8c8c826,stroke:#999999,stroke-width:1.5px
class Email value
Validation¶
All business rules are enforced within the Domain layer. This is the first line of defence - if data reaches the Domain, it must be valid.
DO: Place all business rules and validation in domain entities and value objects
DON'T: Scatter validation across controllers, services, or skip it entirely
Where validation belongs in the Domain:
- Entity factory methods (
Create()) validate construction arguments - Entity business methods validate state transitions
- Value object
Create()methods validate format and range
The Domain layer enforces invariants by throwing exceptions. The Application Layer catches these exceptions and converts them to Result<T> failures.