Domain-Driven Design
Strategic Design Concepts
Explicit boundary within which a domain model applies. Clear semantic context for terms. Customer means different things in Sales vs Support. Model validity bounded by context. Each microservice often one bounded context. Prevents model ambiguity. Context boundaries align with team boundaries. Communication between contexts via published interfaces. Context map visualizes relationships. Foundation of strategic DDD. Prevents big ball of mud.
Shared vocabulary between developers and domain experts. Same terms in code, docs, conversations. No translation between business and technical terms. Class names match business concepts. Evolves with domain understanding. Reduces misunderstandings. Domain experts can read code. Tests use domain language. Bounded to context. Different contexts may use different terms. Living glossary. Model-driven design foundation.
Relationships and integration patterns between bounded contexts. Visualize context landscape. Identify team dependencies. Partnership, shared kernel, customer-supplier, conformist, anti-corruption layer, open host service. Political and organizational dynamics. Integration patterns between contexts. Strategic design tool. Helps identify coupling and cohesion. Basis for microservice boundaries. Updated as organization evolves.
Core, supporting, and generic subdomains. Core: competitive advantage, unique to business, invest heavily. Supporting: necessary but not differentiating, build vs buy. Generic: solved problems (auth, payments), buy or use open source. Resource allocation based on classification. Core gets best developers. Generic uses off-the-shelf. Supporting can be outsourced. Strategic planning tool. Guides build vs buy decisions.
Defining core purpose and value of domain. One-page description of domain model. What makes it valuable. Aligns team on purpose. Guides decisions during development. Prevents feature creep. Describes core domain. Marketing for the model. Stakeholder alignment. Revisited periodically. Keeps team focused. Part of strategic design. Foundation for bounded context definition.
Identifying and protecting core domain. Separate core from supporting and generic. Core domain is what makes system valuable. Invest most in core. Minimize core to essential concepts. Refactor to make core explicit. Generic subdomains extracted. Supporting code separated. Clear focus on what matters. Continuous process. Prevents core from being obscured. Strategic pattern for long-term system health.
Context Mapping Patterns
Two teams collaborate closely on integration. Mutual dependency, coordinated planning. Joint development of integration. Shared success and failure. Aligned release schedules. Common goals. High communication overhead. Works for tightly coupled contexts. Ad-hoc coordination. Both teams accommodate each other. Requires strong relationship. Alternative to more formal patterns. Agile friendly. Shared responsibility.
Shared subset of domain model between contexts. Common code library. Minimize shared portion. Both teams agree on changes. Versioning important. Reduces duplication. High coupling between contexts. Shared schema or API models. Changes require coordination. Use sparingly. Alternative: duplicate code in contexts. Common in monoliths. Microservices avoid this. Suitable for closely aligned teams.
Downstream team (customer) depends on upstream team (supplier). Upstream in control but considers downstream needs. Automated acceptance tests from downstream. SLAs between teams. Supplier commits to interface stability. Customer provides requirements. Versioned APIs. Negotiated features and schedule. Formalized relationship. Clear responsibilities. Supplier protects own model. Downstream accepts upstream model with ACL if needed.
Downstream team adopts upstream model entirely. No translation layer. Downstream has no influence on upstream. Simplest integration. Gives up modeling independence. Upstream model infects downstream. Use when upstream model is good enough. When no negotiating power. Low ceremony. Fast integration. Risk: poor upstream model compromises downstream. Temporary pattern until ACL added. Common with third-party APIs.
Translation layer protecting domain model from external influence. Adapts external model to internal model. Isolates bounded context from upstream changes. Facades, adapters, translators. Prevents leaky abstractions. Preserves ubiquitous language internally. Essential for legacy integration. Third-party API integration. Microservices communicate via ACLs. Cost: additional code layer. Benefits: isolation, independence. Hexagonal architecture outer layer. Domain remains pure.
Well-defined integration API for multiple consumers. Published language: shared communication protocol. Open host: service available to any consumer. RESTful API, GraphQL, gRPC as published language. Versioned, documented, stable. Canonical model for integration. Multiple downstream contexts use same API. Upstream maintains compatibility. OpenAPI spec as published language. Events published to event bus. Supports multiple integrations without customization. Higher upfront cost.
Tactical Patterns - Building Blocks
Objects with identity and lifecycle. Distinguished by ID, not attributes. Mutable over time. Identity persists across state changes. User, Order, Customer are entities. ID often UUID or surrogate key. Entity equality by ID comparison. Track changes and history. Lifecycle: created, modified, archived. State transitions. Identity continuity essential. JPA @Entity, Spring Data. Contrast with value objects.
Immutable objects defined by attributes, not identity. Two objects with same values are equal. No lifecycle or identity. Money, Address, DateRange as value objects. Immutability simplifies reasoning. Thread-safe by default. Replace instead of modify. Can be shared freely. Value equality. Easier testing. Functional programming friendly. C# record types, Java records, Kotlin data classes. Minimize primitives in domain (primitive obsession anti-pattern).
Consistency boundary with root entity. Cluster of entities and value objects. Single entry point (aggregate root). Enforce invariants within aggregate. External references only to root. Transactional boundary. Unit of persistence. Optimize for small aggregates. Order (root) with OrderItems. Repository per aggregate. Eventual consistency between aggregates. Domain events for communication. ID references between aggregates. Prevents complex object graphs. Key tactical pattern.
Representing significant occurrences in domain. OrderPlaced, PaymentReceived, UserRegistered. Past tense naming. Immutable fact. Decouples aggregates. Eventual consistency via events. Event-driven architecture. Event sourcing foundation. Published after transaction commit. Handled asynchronously. Cross-aggregate communication. Subscribers react to events. Audit trail. Time travel debugging. Spring ApplicationEvent, MediatR, EventBus. Separate domain events from integration events.
Persistence abstraction for aggregates. Collection-like interface. Hide database details from domain. Repository per aggregate root. findById, save, delete methods. Query methods return domain objects. Not one-per-table. Domain-centric not data-centric. In-memory collection illusion. Implementations use ORM, NoSQL, etc. Dependency inversion (domain owns interface). Spring Data repositories. Prevents domain coupling to infrastructure. Testable with in-memory implementations.
Complex object creation logic encapsulated. Hide construction complexity. Ensure valid object creation. Aggregate creation. Reconstitution from database. Builder pattern for value objects. Factory methods on aggregate root. Separate factories for complex cases. Invariant enforcement at construction. Alternative: constructors for simple cases. Prevent invalid state. Domain logic for creation. Static factory methods. Abstract factory for families.
Application Architecture Patterns
Domain, application, infrastructure separation. Presentation, application, domain, infrastructure layers. Dependency rule: layers depend downward (or inward). Domain layer independent of infrastructure. Application layer orchestrates use cases. Infrastructure implements interfaces defined by domain. Persistence ignorance. Separation of concerns. Clean architecture variant. Hexagonal architecture variant. Dependency inversion at layer boundaries. Domain at core, infrastructure at edges.
Domain at center with ports and adapters. Ports: interfaces defined by domain. Adapters: implementations of ports. Primary/driving adapters (UI, API). Secondary/driven adapters (database, external services). Domain independent of frameworks. Testable in isolation. Swap adapters without changing domain. Symmetry of inbound and outbound. Alistair Cockburn pattern. Clean architecture aligned. Dependency inversion throughout. Infrastructure pluggable.
Separate models for read and write. Commands change state, queries return data. Write model optimized for consistency. Read model optimized for queries. Eventual consistency between models. Scales read and write independently. Multiple read models for different views. Event sourcing often combined. Materialized views. Denormalized read models. Complex but powerful. Greg Young pattern. Suitable for complex domains. Overhead for simple CRUD. Axon Framework, EventStore.
Store events as source of truth, not current state. Append-only event store. Rebuild state by replaying events. Audit trail inherent. Time travel and debugging. Event versioning challenges. Performance considerations (snapshots). Query current state requires projection. Greg Young, Martin Fowler pattern. CQRS natural fit. Domain events persisted. Event replay for testing. Complete history. Never lose information. Complex operational model. EventStoreDB, Axon Server, Kafka.
Operations not belonging to entities or value objects. Stateless domain logic. Cross-aggregate operations. Business rules spanning entities. Not repositories (infrastructure services). Not application services (use case orchestration). Example: TransferFunds between accounts. Interface in domain layer. Named with verbs (not entities). Prevent anemic domain (use sparingly). Domain logic not fitting elsewhere. Pure domain concepts. No infrastructure dependencies.
Use case orchestration layer. Thin, no business logic. Delegates to domain objects. Transaction boundaries. Coordinates aggregates via repositories. Calls domain services. Publishes domain events. DTO to domain model translation. Security enforcement. Use case per service method. Command handlers in CQRS. MediatR handlers. Spring @Service (application layer). Prevents domain logic leakage. Testable independently. Gateway to domain.
Managing Complexity
Business rule encapsulation as reusable objects. Predicate/criteria as first-class objects. Composable via AND, OR, NOT. IsSatisfiedBy method. Use in queries and validation. Separate rule from entity. Reusable across aggregates. Example: PremiumCustomerSpecification. Can be persisted. Declarative business rules. Testing rules independently. Alternative to if-else chains. Domain-driven queries. ORM query generation from specification.
Domain rules as first-class objects. Strategy pattern applied to domain. Tax policy, pricing policy, shipping policy. Pluggable business rules. Different implementations per context. Rules externalized from entities. Testable policies. Change policy without changing entities. Configuration-driven policies. Rule engines (Drools). Policy evaluation service. Separates what (domain) from how (policy). Simplifies entity logic.
Cross-aggregate communication via events. Decouples aggregates. Eventual consistency. Event bus or mediator. Publish after successful transaction. Subscribers don't affect publisher. Domain events vs integration events. In-process events first. Outbox pattern for reliable publishing. Event versioning. Idempotent subscribers. Testing via event verification. Audit trail. Event storming derives events. MediatR, Spring Events, EventBus.
Long-running business processes across aggregates. Orchestration or choreography. Compensating transactions for failures. Order processing saga: reserve, payment, fulfillment. State machine for saga. Eventual consistency. Not ACID transactions. Temporal coupling managed. Axon Saga, MassTransit Saga. NServiceBus Saga. Stateful coordination. Timeout handling. Partial failure handling. Alternative to distributed transactions. Microservices coordination.
Orchestrating multi-step workflows. Central coordinator for saga. State machine tracking progress. Knows all steps and order. Commands aggregates. Reacts to domain events. Timeout and error handling. Example: OrderProcessManager. Persistent process state. Resume after failures. Explicit sequencing. Visibility into process. Alternative to choreography. Workflow engine integration. More control than event-based choreography. Trade-off: central point of coordination.
Validation and invariant enforcement. Aggregates enforce invariants. Value objects validate at construction. Domain events for cross-aggregate rules. Guard clauses in domain methods. Fail fast on invalid state. Business rule validation. Specification pattern for complex rules. No invalid state possible. Self-validating domain model. Prevent anemic domain. Domain-driven validation, not annotation-based. Invariants always held. Testing validates rules.
Implementation Practices
Collaborative domain modeling workshop technique. Domain experts and developers together. Orange stickies for domain events. Blue for commands. Yellow for aggregates. Purple for policies. Red for issues. Timeline on wall. Discover ubiquitous language. Identify bounded contexts. Find aggregates and commands. Interactive, fast. Alberto Brandolini technique. Big picture, process modeling, software design levels. Remote event storming tools (Miro). Foundation for implementation.
Identifying natural domain boundaries. Language inconsistencies signal boundaries. Organizational boundaries often align. Different use cases for same data. Conway's Law (team boundaries = system boundaries). Subdomains map to contexts. Event storming reveals contexts. Context map visualizes. Iterative discovery process. Avoid premature splitting. Prefer larger contexts initially, split as needed. Linguistic boundaries primary. Technical boundaries secondary.
Unit, integration, and acceptance testing in DDD. Unit tests for value objects, entities, domain services (pure domain logic). Integration tests for repositories, application services. Acceptance tests in ubiquitous language (BDD). Test aggregates in isolation. In-memory repositories for testing. Domain events verification. Specification pattern testability. Test-driven domain modeling. Given-When-Then for behavior. ArchUnit for architecture rules. Prevent infrastructure coupling in tests.
Incremental adoption in existing systems. Start with core domain. Identify aggregates in existing code. Extract value objects from primitives. Add domain events gradually. Anti-corruption layer for legacy. Strangler fig pattern. Parallel models during transition. Refactor toward deeper insight. Don't big-bang rewrite. Brownfield DDD. Learn domain continuously. Breakthrough moments. Model evolution over time. Measured approach. Business value along the way.
Mapping bounded contexts to microservices. One bounded context per service (ideal). Share nothing between services. Service owns data. Anti-corruption layers at boundaries. Domain events via message bus. Separate deployments. Organizational autonomy. DDD strategic design guides decomposition. Avoid distributed monolith. Eventually consistent integration. Saga for cross-service transactions. Context map = service map. DDD first, microservices after. Not every context needs separate service.
Architecture Decision Records for key decisions. Context maps visualizing bounded contexts. Ubiquitous language glossary. Event catalog. Aggregate documentation. Domain storytelling diagrams. Living documentation from tests. Code as primary documentation. PlantUML for diagrams. C4 model for architecture. Lightweight, value-adding documentation. Avoid redundant docs. Keep docs close to code. Version control documentation. Architectural Fitness Functions. Evolve with understanding.
