Domain-Driven Design with Protean
The Foundation
This is the simplest way to use Protean and the foundation for the other two approaches. Start here if you're new to Protean or building a straightforward application.
Overview
In the pure DDD approach, you model your domain with aggregates, entities, and value objects, then use application services to orchestrate use cases. Repositories handle persistence, and domain events propagate side effects to event handlers.
This approach follows the patterns from Eric Evans' Domain-Driven Design (the "Blue Book"). There are no commands or command handlers — application services receive requests directly and coordinate the domain logic.
Request Flow
sequenceDiagram
autonumber
participant API as API Layer
participant AS as Application Service
participant R as Repository
participant Agg as Aggregate
participant EH as Event Handler
API->>AS: Call use case method
AS->>R: Load aggregate
R-->>AS: Aggregate
AS->>Agg: Invoke domain method
Agg->>Agg: Mutate state, raise event
AS->>R: Persist aggregate
R-->>EH: Domain event dispatched
EH->>EH: Handle side effects
AS-->>API: Return result
- The API layer calls a method on an Application Service
- The Application Service loads the aggregate from a Repository
- It invokes the appropriate domain method on the aggregate
- The aggregate mutates its state and raises domain events
- The Application Service persists the aggregate back through the repository
- Domain events are dispatched to Event Handlers for side effects
- The result is returned synchronously to the caller
Elements You'll Use
| Element | Purpose |
|---|---|
| Aggregates | Root entities that encapsulate business logic and enforce invariants |
| Entities | Objects with identity that live within an aggregate |
| Value Objects | Immutable descriptive objects (Money, Address, Email) |
| Fields | Typed attributes on domain elements |
| Application Services | Orchestrate use cases with @use_case methods |
| Repositories | Persist and retrieve aggregates |
| Events | Record state changes as domain events |
| Event Handlers | React to events with side effects |
| Subscribers | Consume messages from external message brokers |
| Domain Services | Coordinate logic spanning multiple aggregates |
Guided Reading Order
Work through these guides in order to build a complete understanding of the DDD approach:
Define your domain model
| Step | Guide | What You'll Learn |
|---|---|---|
| 1 | Set Up the Domain | Register elements, initialize and activate your domain |
| 2 | Aggregates | Define your root entities and aggregate boundaries |
| 3 | Entities | Add child objects with identity |
| 4 | Value Objects | Model immutable descriptive concepts |
| 5 | Fields | Understand the field system and data types |
| 6 | Relationships | Connect entities with HasOne, HasMany, Reference |
Add business rules and behavior
With your domain model defined, you need to protect it with business rules and give it the ability to communicate what happened:
| Step | Guide | What You'll Learn |
|---|---|---|
| 7 | Validations & Invariants | Enforce business rules |
| 8 | Aggregate Mutation | Change aggregate state safely |
| 9 | Raising Events | Emit domain events from aggregates |
Wire the application layer
With a rich domain model in place, you can orchestrate use cases and persist state:
| Step | Guide | What You'll Learn |
|---|---|---|
| 10 | Application Services | Orchestrate use cases with @use_case |
| 11 | Persist Aggregates | Save aggregates through repositories |
| 12 | Retrieve Aggregates | Load aggregates by ID or query |
React to state changes
To handle side effects and coordinate across aggregates:
| Step | Guide | What You'll Learn |
|---|---|---|
| 13 | Event Handlers | React to domain events |
| 14 | Subscribers | Consume messages from external brokers |
| 15 | Domain Services | Coordinate cross-aggregate logic |
| 16 | Testing | Test your domain model and application layer |
Relevant Patterns
These patterns complement the DDD approach:
| Pattern | What It Covers |
|---|---|
| Design Small Aggregates | Keep aggregates focused and performant |
| Encapsulate State Changes | Protect aggregate internals with controlled mutation |
| Replace Primitives with Value Objects | Use rich types instead of raw strings and numbers |
| Validation Layering | Apply validation at the right layer |
| Thin Handlers, Rich Domain | Keep handlers lean, push logic into the domain model |
| Testing Domain Logic in Isolation | Test domain rules without infrastructure |
| Organize by Domain Concept | Structure your project around business concepts |
| Creating Identities Early | Generate aggregate IDs before persistence |
When to Evolve to CQRS
Consider moving to CQRS when:
- Your read and write models have diverging requirements
- You need read-optimized views (projections) independent of your aggregates
- You want explicit, auditable commands representing user intent
- You need asynchronous command processing
- Your application would benefit from separating the write path from the read path
See the Architecture Decision guide for a systematic decision framework.