DDD: Modeling Problems in Software
Bounded Context
Solution Space ConceptDescribes how the software and software development gets broken down.
The Bounded Context is shaped to fit the Sub-Domain. Carpet is shaped to fit the floor.
A Bounded Context does not necessarily mean a separate application. To make it clear setup your application to be explicit as to where the contexts are.
- Where is the model valid?
- Concepts that make sense in one part of the application may not make sense in another.
- Even if they have the same name
- Even if they refer to the same thing
- A Client in the Appointment Context is different than a Client in the Billing Context
- Appointment Context:
- Name
- Identity
- Billing Context:
- Name
- Identity
- Credit Cards
- Address
- Billing Validation
- Credit Validation
- Each Bounded Context gets its own:
- Team
- Code Base
- DB Schema
Sub-Domain
Problem Space ConceptDescribes how you’ve chosen to break down the business or domain activity.
Context Maps
A global view of the application as a whole. Each bounded context fits within the Context Map to show how they should communicate with each other and how data should be shared.Which aspects of the system can a team modify on their own and which are shared dependencies must they coordinate with other teams.
Shared Kernel
A way to share concepts across Domains. For instance Authentication requires a User be defined once and authenticated the same. However, Appointment’s and Billing will both use it. Therefore, any changes being made to User must be coordinated between all the teams involved.Ubiquitous Language
A single shared language for a Context for the whole team:- Devs
- Business/Domain Owners
- Project Managers
Everyone uses the same words to mean the same things.
Ubiquitous language must be used everywhere inside the bounded context, from conversations to code.
Definitions
- Problem Domain
- The specific problem the software is trying to solve.
- Core Domain
- The key differentiator for the customer’s business – something they must do well and cannot outsource.
- Sub-Domains
- Separate applications or features your software must support or interact with.
- Bounded Context
- A specific responsibility, with explicit boundaries that separate it from other parts of the system.
- Context Mapping
- The process of identifying bounded contexts and their relationships to one another.
- Shared Kernel
- Part of the model that is shared by 2 or more teams, who agree not to change it without collaboration.
- Ubiquitous Language
- A language using terms from the domain model that programmers and domain experts use to discuss the system.
Elements of a Domain Model
Domain
The Domain Layer is responsible for representing concepts of the business situation, and business rules. State that reflects the business situation is controlled and used here, even though the technical details of storing it are delegated to the infrastructure. This layer is the heart of business software.The Domain Layer is the heart of the business software.
Focus on the behaviors not the attributes.
Anemic Domain Models
Domain model that is focused on the state of its objects. Basic CRUD. Commonly, these are tables in DB mapped to objects with no functionality.- This is the antithesis of DDD.
- Anemic is perfectly fine for CRUD.
- Anti-pattern in DDD
- Bags of getters and setters
- No domain logic in the domain objects.
- Instead there are set of service objects which capture all the domain logic. These services live on top of the domain model and use the domain model for data.
Rich Domain Models
A collection of objects that expose behavior.Entities
Have identity and are mutable. Defined by their thread of continuity and identity.- Uses GUID, Code controls the creation of the entity
- Track
- Locate
- Retrieve
- Store
- Most likely to become a table
- Are inside of a Domain and Not used for CRUD
- They require more complex handling
- Shouldn’t have a lot of different business logic in it.
Design an Entity with the identity and life cycle in mind. Identity isn’t just an ID field.
Identity matching can become so complex that it’s sometimes appropriate to have another object or set of functions be responsible for it.
Entities might delegate logic to value objects. Entity, “What should be done next?” Some service gets invoked to answer that question.
Entity Shouldn’t Have an Equality Comparison
A value object should always have a well-defined equals method. The concept is not quite right for entities.- Is this referring to the same entity?
- Have I got 2 properties of the same object?
- What do you mean?
Are these 2 entities the same customer? Did we mess up and enter him/her twice?
Do I have the same ID for 2 different sets of data? We’ve got a distributed DB so things didn’t update across all nodes correctly.
Reference Type Entities (aka Reference Entities)
Read only types used as references inside of a Domain.- Uses Int, DB controls the creation of the entity
- Not edited inside the Domain
We can reuse names in different Domains because of Namespaces.
Factory Methods
Static methods that do the creation of the entity and also validate the input. They help avoid entities being in an inconsistent state.Relationships
Try to use unidirectional relationships. Bi-directional relationships can often lead to confusion.A Relationship (or Association) should be part of a type’s definition. If 2 types have a bi-directional relationship it means that both types cannot be defined without one another. Make sure that’s the case.
A bidirectional association means that both objects can be understood only together. When application requirements do not call for traversal in both directions, adding a traversal direction reduces interdependence and simplifies the design.
Try to start with One-Way relationships.
YAGNI
Value Objects
Objects that are defined by their values.- Measures, quantifies or describes a thing in the domain.
- Identity is based on composition of values
- Immutable
- Compared using all values
- No side effects
- Any methods should compute things not change the state
Money is a great example of a value object:
- Company worth
- ID: 2
- Worth Unit: US Dollars
- Worth Amount: 50000000
- Date: 2016-04-25
We should use Value Objects instead of Entities wherever possible. Even when a domain concept must be modeled as an Entity, the Entity’s design should be biased toward serving as a value container rather than a child Entity container.
You’re design’s will have Entities with very little logic of their own or very few primitives as their properties. Instead Entities will have lots of properties that are themselves Value Objects.
Sometimes when you’re looking at an entity there might be a couple of properties that seem to always go together. You might be able to bundle these properties into a single Value Object.
Value Objects are a better place to put methods and logic because we can do our reasoning without side effects and identity.
Value Objects are easier to test.
The Entity becomes the orchestrator of the Value Objects but can’t complete a lot of operations on its own.
Domain Services
When an operation is important to a model but does not belong on any Entity or Value Object a Domain Service is appropriate.Domain Services serve as orchestrators for operations that require several different collaborating Entities or Value Objects.
Good Domain Services:
- Not a natural part of an Entity or Value Object
- Have an interface defined in terms of other domain model elements
- Are stateless (but may have side effects)
Live in the Core of the application.
Definitions
- Anemic Domain Model
- Model with classes focused on state management. Good for CRUD.
- Rich Domain Model
- Model with logic focused on behavior, not just state. Preferred for DDD.
- Entity
- A mutable class with an identity (not tied to its property values) used for tracking and persistence.
- Immutable
- Refers to a type whose state cannot be changed once the object has been instantiated.
- Value Object
- An immutable class whose identity is dependent on the combination of its values.
- Services
- Provide a place in the model to hold behavior that doesn’t belong elsewhere in the domain.
- Side Effects
- Changes in the state of the application or interaction with the outside world (e.g. infrastructure)
Aggregates in Domain-Driven Design
Aggregate
An aggregate is a cluster of associated objects that we treat as a unit for the purpose of data changes.Consist of one or more Entities or Value Objects that change together.
We need to treat them as a unit for data changes and we need to consider the entire aggregates consistency before we apply changes.
We treat a set of changes to everything in the Aggregate as a single transaction. Every Aggregate must have an Aggregate Root.
Every time you update an Aggregate you should do so in a transaction so all members of the Aggregate are updated.
Aggregates should follow ACID:
- Atomic
- Consistent
- Isolated
- Durable
Invariant
An invariant is a condition that should always be true for the system to be in a consistent state.It’s the Aggregate Root’s responsibility to maintain its invariants.
For instance a Schedule Aggregate (in this instance the Root) should check if a Doctor or Room is double booked. Checking for the double booking of a Doctor or Room is the Invariant. The Schedule is responsible for including this rich behavior.
Examples of Invariants:
- The speed of light
- Total items on purchase order do not exceed a limit
- Two appointments do not overlap one another
- End date follows begin date
Aggregate Root
Aggregate Root is the parent object of all the members of the Aggregate.You can have an Aggregate with just one object or only an Aggregate Root.
When evaluating if an object should be treated as an Aggregate Root, think about if deleting the object should trigger a cascade of deletion. If so it’s a good indication that you have an Aggregate Root.
Another way to think about Aggregate Roots, does it make sense to have this object detached from its parent? If so it might be an Aggregate Root.
Aggregate Relationships
Aggregates serve as boundaries between logical groupings in our application. We enforce these boundaries by prohibiting direct references to objects in an Aggregate that are not the Root.If our Aggregate uses Entities or Values Objects not found in the Aggregate it should be a reference to them using the ID of the Entity or Value Object.
Aggregate Tips
- Aggregates are not always the answer
- Aggregates can connect only by the root
- You can use Foreign Keys inside a non-Root entities
- Too many Foreign Keys to non-root entities may suggest a problem
- Aggregates of one are acceptable
- Rule of Cascading Deletes: a way to tell if you have a correct Aggregate Root is if deleting it will delete should other Aggregates.
Definitions
- Aggregate
- A transactional graph of objects
- Aggregate Root
- The entry point of an aggregate which ensure the integrity of the entire graph
- Invariant
- A condition that should always be true for the system to be in a consistent state
- Persistence Ignorant Class
- Classes that have no knowledge about how they are persisted
Repositories
Repository
Applying model first design and separation of concerns means pushing persistence behavior into its own set of abstractions, referred to as Repositories.Only Aggregate Roots should be available via global requests. Repositories provide this access and through omission prevent access to non-aggregate objects except through their aggregate roots. They give you the ability to constrain the data access so you avoid lots of random data access code throughout your application.
Repositories can be responsible for a lot of the grunt work behind the scenes. If we need to match up some data across entities and value objects the repository can do this:
- Get Schedule For Date
- Get Schedule Id For Clinic
- Get Appointments
- Get Highlights
- Add Highlights to Appointments
- Create Schedule Object and pass Appointments into the Schedule’s constructor
- Return the Schedule object
Unit of Work
Handles the coordination of multiple Repositories under 1 transaction.Object Life Cycles
No Persistence:- Create
- Do Stuff
- Destroy
With Persistence:
- Create
- Reconstitute from Persistence
- Do Stuff
- Save Changes to Persistence
- Destroy
Repository are used to handle the life cycle of your persistence objects without the objects having to know anything about the persistence.
Persistent Ignorant Objects: objects that don’t know how they get persisted.
“A repository represents all objects of a certain type as a conceptual set… like a collection with more elaborate querying capability.”
Repository Tips:
- Think of it as in-memory collection
- Implement a known, common access interface
- IRepository: Standard CRUD (add, remove, update, list)
- Methods for add & remove
- Methods that predefine criteria for object selection
- IScheduler.GetScheduleAppointmentsForDate returns a schedule object
- Repos for aggregate roots
- Client focuses on the model, repo focuses on persistence
Repository Benefits:
- Provides common abstraction for persistence
- Promote Separation of Concerns
- Communicates Design Decisions
- Enable Testability
- Improved Maintainability
Client code can be ignorant of repository implementation but developers cannot.
Common Repository Blunders:
- N+1 Query Errors
- One query to get the count and a query for each individual item in the list
- Inappropriate use of eager or lazy loading
- Fetching more data than required
- Do we need all the columns in the table?
IRepository can be useful but not necessary:
- Contains the common CRUD operations
- Implemented as a base for other Repositories
- Common Root specific Repository:
- Root : IEntity
- RootRepository: IRepository<Root>
- More DDD focused since we are constraining its use to Roots
- Generic Repository:
- Repository<TEntity> : IRepository<TEntity> where TEntity : class, IEntity
- Allows a dev to use the repository for non-Root Entities
- Generic Repository with Aggregate Root constraint:
- IAggregateRoot: IEntity
- Root: IAggregateRoot
- Repository<TEntity> : IRepository<TEntity> where TEntity : class, IAggreagateRoot
- var result = new Repository<NonRoot>().GetById(1) can’t be done
Factories
- Factories create new objects
- Repositories use factories to create objects
- Factories have no persistence whereas Repositories do.
Definitions
- Repository:
- A class that encapsulates the data persistence for an aggregate root
- ACID:
- Atomic: everything or nothing gets persisted
- Consistent: the constraints on the data are applied, any updates bring the database from one valid state to another valid state
- Isolated: if 2 different aggregates are being committed at the same time they don’t conflict as they’re being committed
- Durable: once the transaction occurs its permanent
Domain Events and Anti-Corruption Layers
Domain Events
Domain Events provide a way to describe an important activity or state changes that occur in the system. Other parts of the domain can respond to these events in a loosely coupled manor. The objects that raise the events don’t need to worry about the behavior that needs to occur when the event happens. Also, the event handling objects don’t need to know where the event came from.Events can also be used to communicate outside of our domain.
Domain Events are encapsulated as objects. In UI events are written as delegates but in DDD events are first class members of the domain model.
“We should use a domain event to capture an occurrence of something that happened in the domain.”
The Domain Events should be a part of our Ubiquitous Language. Everyone should know what you’re talking about when you say, “Appointment Confirmed Event” was raised.
Instead of having all the behavior that might need to occur be defined in one place we can create an event and broadcast it out. Any class that needs to do something with that event is free to do so. We can separate out classes and adhere to the Single Responsibility Principal.
A Domain Event is a record about something that occurred in the past and might be of interest to other parts of our application.
Domain Event Tips:
- “When this happens, then something else happen.”
- Also listen for:
- “If that happens…”
- “Notify the user when…”
- “Inform the user if…”
- Domain Events represent the past
- Typically they are immutable
- Name events based on terms from the Ubiquitous Language describing clearly what occurred
- If they are fired as part of a command on a domain object be sure to use the command name
- YAGNI: only create events as you need them in your model
- Don’t create events unless you need some behavior to occur when the event takes place and you want to decouple the behavior from its trigger. Only needed when the behavior that’s needed in response to the event does not belong in the class that triggered it.
Designing Domain Events:
- Each event is its own class
- Include when the event took place
- Create a special interface with common event properties
- Public interface IDomainEvent
- DateTime DateTimeOccurred {get;}
- Capture event-specific details
- What would you need to know to trigger this event again?
- Consider including the identities of any aggregates involved in the event
- Event fields are initialized in constructor
- No behavior or side effects
Examples:
- User Authenticated
- Appointment Confirmed
- Payment Received
Hollywood Principal: “Don’t call us, we’ll call you.” Have classes not responsible for something broadcast events to other classes to handle behavior not suitable for the class sending the event.
Break your project out into the following folders and uses:
- Events:
- Models of the events that happened
- Handlers:
- Classes that respond to particular events
- NotifyUIAppointmentConfirmed : IHandle<AppointmentConfirmed>
- Interfaces:
- Where all your interfaces go, meh
- Model:
- The Aggregate(s)
- Repositories:
- Where all your repositories and unit of work classes go
The DomainEvents class can be found on Udi Dahan’s website. http://udidahan.com/2009/06/14/domain-events-salvation/
This class is used to route events inside your application.
A Schedule contains many Appointments. Since we don’t want the Appointment classes knowing about the Schedule class we broadcast an event that the Schedule is subscribed to.
Other classes outside of the domain can handle the events as well like the UI. Even your webpage can be updated using SignalR.
Domain Event Boundaries
- Create separate event objects for specific clients
- Create cross-system events for external clients
- Internal ID’s are not applicable for system’s outside our boundary
- Add details for external systems instead of having them request all the details separately
- Define specific application events for the UI layer
Anti-Corruption Layers
Anti-Corruption Layers are used to translate and insulate between a bounded context and foreign systems.When you design a new system you need to make sure other Bounded Context’s and Legacy App’s assumptions and design decisions do not bleed into your model. For instance, if another system includes a customer, even if that customer refers to the same actual business customer, it’s likely that it will be modeled differently than in your system. It’s best to have a layer that can translate to and from other system’s models.
- Translate between the foreign system and your own
- Clean up the way you communicate with the other system
- Façade
- Adapter
- Custom Translation classes or services
“Even when the other system is well designed, it is not based on the same model as the client. Often the other system is not well designed.”
Definitions
- Domain Event
- A class that captures the occurrence of an event in a domain object
- Hollywood Principal
- “Don’t call us, we’ll call you”
- Inversion of Control (IoC)
- A pattern for loosely coupling a dependent object with an object it will need at runtime
- Anti-Corruption Layer
- Functionality that insulates a bounded context and handles interaction with foreign systems or contexts