Domain Driven Design — Domain Model and its Components

Harish Subramaniam
12 min readApr 18, 2020

In my last story , I had gone through what are domains . Domains as a recap are core functionalities of the current business model . Most of the time, domain experts spend a lot of effort discovering in-depth about domains in the business ecosystem to help identify the problem in detail.

More information about “Domains in DDD” with example can be found in the my article

How to Domain model ?

Once domains are identified and we have enough information to structure the domains , we start domain modelling. So let’s start.

Let me put the diagram from the previous story so that we can set the context for the domain modelling.

Book borrowing domain discovery

As part of the above activity , we successfully did the domain discovery of the book borrowing domain.

But do you think the actors like book, user subscription belong to the book borrowing domain ? The answer is No.

If you think about it , these actors belong to their own domain as follows :

  • Book belongs to Book Management domain
  • User subscription belongs to the Subscriptions domain
  • Users belong to Identity/Users domain.

So the first activity of domain modelling would be to identify whether the actors belong to the same domain or not .

One way to analyse would be to understand what the Single Responsibility of the domain is.

The Single Responsibility of book borrowing domain is to maintain a log of what books were borrowed by whom and when it can be released for others to borrow.

The responsibilities like :

  • whether the User has successfully on boarded our library system — User Domain
  • whether the user is disqualified to be part of the system — User Domain
  • whether the subscription of the user is suspended or not — Subscription Domain
  • whether a book is available in system or waiting for it to be approved by the library to be available for people to borrow. — Book Domain

These are out of scope and no where handled in the Book borrowing domain. You might need information about these actors to process a borrowing of the book but the responsibilities mentioned above are not carried out in the Book Borrowing Domain.

Without the availability of these actors the book borrowing process is incomplete but putting them in the same domain of book borrowing will not make the domain Single Responsibility.

So how do we model them ? Lets draw another diagram

Domain Model and Interaction Points of Library Subsystems/Bounded Contexts

Wow !!!! Such a big diagram and so much text . Whats all this about ?

Don’t worry let’s decipher the diagram one by one.

As mentioned previously , a domain focuses on SRP (Single Responsibility Principle) which means the domain can do only one thing about the business , either manage books , or manage book bookings , or manage user subscriptions or manage users . So a new term arises by literally combining “Single Responsibility” and “Domain” . Its called “Bounded Context” where Bound means limit and context means sub-system/domain.

So Bounded Context = Single Responsibility + Domain.

Therefore each of sub-systems/domain have a Bounded Context

When you look at each Bounded Context in the above diagram, you would notice couple of terms :

  • Entity
  • Aggregate
  • Domain events
  • Commands
  • Handler
  • Queries
  • Domain mapped events

Well thats good if you noticed. Let’s talk about them

Entity

Entity in DDD is referred to an object which is not identified by its attributes but by assigning it a unique id . This unique id is unique across the entire Library ecosystem. A good representation of the entity id would be “<<ObjectName>> <<delimiter>><< UUID>>” . This way there is no way we would have two entities with the same id if they are generated at the same time.

Entities are objects which are persisted in the database using domain events . In DDD entities aren’t saved by mutating the state like traditional CRUD but they are persisted as a log of events . The events are replayed to rebuild the state of the entity during entity reads.

In the above diagram, examples of entities would be

BookBorrowingEntity: This is used to store information uniquely for each book borrowing booking in the library.

UserSubscriptionEntity : This is used to store information uniquely for each subscription bought by the user in the library

UserEntity : This is used to store information uniquely for each user registered in the library

BookEntity: This is used to store information uniquely for each book registered in the library to be available for borrowing.

There can be multiple entities in the same bounded context. For example in the user subscription bounded context we can have SubscriptionPlan entity to uniquely identify subscription plans .Each user subscription has a subscription plan assigned. If we have entity connected with another , the connection is built using entity ids like the diagram below.

Connecting entities

There can be few objects in the sub-system which are neither represented uniquely in the system nor need persistence . They are just used to support an entity or aggregate( we will cover this shortly) . These are called “Value Objects” . Value Objects are immutable in nature . Any entity/aggregate generates a new copy of the value object when asked for. A good example of a value object is SubscriptionAmount in a SubscriptionPlan.

SubscriptionAmount has 2 attributes , currency and value. If both attributes are the same for 2 different SubscriptionAmount objects (for example a unit of 35 and currency “USD”) they mean the same. SubscriptionAmount does not need a uniqueId to be represented. SubscriptionAmount supports the SubscriptionPlan object.

Aggregate

When you want to act on a User Subscription for a user you not only need duration of subscription, existing subscription status (if any) as attributes but also user id ( indicating who is the user this subscription is created for) and subscription plan id the user is buying to access the library resources. This composition of information is referred to as “Aggregate” in DDD. It defines the boundary of the information needed to execute business related information in a domain.

User Subscription Aggregate = User Subscription Info + Plan Id + User Id

If you look closely , the right hand side corresponds to UserSubscriptionEntity.

UserSubscriptionEntity = User Subscription Info + Plan Id + User Id

Well , I purposely kept the equations separate. Most of the times if the business domain is segregated and designed correctly , you will end up having a single entity in the aggregate but you could have multiple entities unless it’s absolutely necessary. A single entity to an aggregate helps in keeping the transactional boundary small , increases horizontal scalability , minimises database locking and helps keeping away from concurrency issues.

If you have multiple entities like (multiple objects to be saved as part of the same transaction : NOT RECOMMENDED ), then we define one entity to be the guard keeper of the aggregate and its called aggregate root.

If there is only one entity , then the Aggregate and Aggregate Root refer to the same entity.

It is encouraged and recommended practice to have multiple aggregates in the same bounded context.

Multiple Aggregates in the Same Bounded Context

It is recommended to have multiple smaller aggregates in the same bounded context with one on one mapping to persistent entities.

In User Subscription Domain following are the aggregates deduced :

UserSubscriptionAggregate = UserSubscriptionEntity = UserSubscriptionInfo + Plan Id + User Id

SubscriptionPlanAggregate = SubscriptionPlanEntity = PlanId + PlanDetails + SubscriptionAmount

In above aggregate design , UserSubscriptionAggregate depends on SubscriptionPlanAggregate but you won’t save a plan while saving a user subscription . Their save operations are mutually exclusive with no dependency apart from the subscription plan’s existence to save a subscription which is defined by the planId in the UserSubscriptionEntity.

If both the correlated aggregates need to be saved pretty much the same time , then eventual consistency of the aggregates will be the only way possible to reflect latest data.

For example , if there is ShoppingCartAggregate which contains a bunch of line items for purchase then aggregate design would look like follows :

ShoppingCartAggregate = shopping cart id + list of line items id + shopping cart details.

LineItemAggregate = shopping cart Id + line item id + product id + quantity

When the first line item is created in the shopping cart , the eventual consistency will be applicable with the series of events fired by the caller application

a) CreateShoppingCartEvent -> Creates empty shopping cart with a shopping cart Id using ShoppingCartAggregate

b) CreateLineItemEvent -> Creates the first line item using LineItemAggregate

c) UpdateShoppingCartEvent -> Updates the shopping cart with the created line item using ShoppingCartAggregate

Please note that the changes on the shopping cart will be eventually applied after b) and c) is successful . Till then the shopping cart would remain empty.

Commands

Commands are business related/domain related instructions , triggered by an internal process in the Bounded Context. In DDD, Commands are applied only on aggregate root (a persistent entity) or the only entity in the aggregate.

When I instruct someone to buy a movie ticket , I can either get the movie ticket booked and I can enjoy the movie or if the movie is fully booked , then there is no ticket available for that time and I wont be able to book the ticket. The decision is taken on the current state of the “movie tickets booking” (MovieTicketBookingAggregate).

Similarly , when a command is issued to perform an activity on an aggregate root , then validation of the command happens against the current state of the entity/aggregate root . If the validation rejects the command , then nothing is persisted . If the validation is accepted then the domain event related to the command is stored in the database . This event becomes the newest member of the domain event stream of the aggregate root and the entire set of domain events will be re-playable at any point to re-build the state of the aggregate root/persistent entity from scratch.

So let’s decipher what commands can be in the library system for each aggregate/aggregate root of different sub systems/bounded context .

User Bounded Context

  • UserAggregate — AddUserCommand, DeactivateUserCommand , UpdateUserCommand , ActivateUserCommand , UpdateEmailUserCommand, PasswordResetUserCommand
  • AdminAggregate AddAdminUserCommand , UpdateAdminUserCommand, DeactivateAdminUserCommand

User Subscription Bounded Context

  • UserSubscriptionAggregate — AddUserSubscriptionCommand, DeactivateUserSubscriptionCommand , UpgradeUserSubscriptionCommand, DowngradeUserSubscriptionCommand, UpdateExpirationDateUserSubscriptionCommand
  • SubscriptionPlanAggregate — AddSubscriptionPlanCommand,UpdateSubscriptionPlanCommand, DeactivateSubscriptionPlanCommand

Book Bounded Context

  • BookAggregate — CreateBookCommand , UpdateBookAvailabilityCommand , UpdateBookCommand

Book Borrowing Bounded Context

  • BookBorrowingAggregate- CreateBookBorrowingRequestCommand, CheckExpiryBookBorrowingCommand , UpdateBookBorrowingRequestCommand

Domain Events

Domain events are result of successful execution of the commands . They represent a state change of an aggregate root/aggregate with a single entity.

Domain events are persisted for an aggregate root/persistent entities/aggregates with one entity.

Some design considerations while defining domain events :

  • Concise , Domain Related , no Business Abstractions

The concept of designing events generated by the system with an abstraction over the business is great but not the natural way of representing a business domain.

For example a PersonAddedEvent with a user type = admin is great in terms of abstraction but I like to say AdminCreatedEvent is much more concise to the business domain (Admin Management) because admin users have much more privileges in the business than a normal user.

A normal user can be a subscriber to a platform so you can have domain related events for Subscriber Domain like SubscriberAddedEvent , SubscriberDeActivatedEvent , SubscriberUpdatedEvent.

  • No Vendor Locks , No Vendor Coupling , Understanding your domain better

Domain Driven Design inclines us to naturally represent sub-systems of a big business and decompose them in such a way that the flow of information between different sub-systems is an extension of a human mind which makes it easy to visualise and code. It helps us to decouple the technology and implementation dependencies and helps us to focus on the actual business problem area. For example , in a payment domain , I would represent events like PaymentInitiatedEvent , PaymentDeclinedEvent , PaymentSuccessEvent , PaymentCreditCheckEvent rather than hooking the PaymentGateway vendor name in the event for example (PaymentDeclinedWithXYZVendorEvent). I would also not tightly couple the vendor implementation and vendor related events in my domain because the vendor doesn’t belong my domain . If we do that , then it’s an architecture smell promoting vendor locking and tight coupling between systems . We then fail to understand what our domain actually is and what is our domain capable of doing because we are at the mercy of vendor’s domain and its capabilities . This makes us be in a situation that we cannot replace sub-system implementations from Vendor X to Vendor Y.

Designing Domain Events for the Library system

Keeping the above 2 concepts in mind, let us design the domain events for our sub-systems/bounded contexts.

User Bounded Context

  • UserAggregate — UserAddedEvent, UserDeactivatedEvent , UserUpdatedEvent , UserActivatedEvent , UserUpdateEmailEvent, UserPasswordResetEvent, UserAddedThroughFacebookEvent( Not a good domain event).
  • AdminAggregate AdminUserCreatedEvent , AdminUserUpdatedEvent, AdminUserDeactivatedEvent

User Subscription Bounded Context

  • UserSubscriptionAggregate — UserSubscriptionAddedEvent, UserSubscriptionDeactivatedEvent , UserSubscriptionUpgradedEvent,UserSubscriptionDowngradedEvent, UserSubscriptionExpireDateChangeEvent, UserSubscriptionAddedInVendorXEvent( Not a good domain event).
  • SubscriptionPlanAggregate — SubscriptionPlanAddedEvent,SubscriptionPlanUpdatedEvent, SubscriptionPlanDeactivatedEvent

Book Bounded Context

  • BookAggregate — BookCreatedEvent , BookUnavailableEvent, BookAvailableEvent, BookCategoryChangeEvent

Book Borrowing Bounded Context

  • BookBorrowingAggregate- BookBorrowingCreatedEvent, BookBorrowingExpiredEvent , BookBorrowingFineOverdueEvent

Handler

Given the current state of the aggregate root, some process needs to do the job of validating an incoming command, processing a command , reply rejections of a command , persisting a domain event if the command execution is successful.

Let’s name the process as the Handler. When a command is issued to be acted on the current state of the aggregate root , the handler consumes the command and tries to reply back with an answer positive ( that the command is processed successfully) or negative (that the command is rejected due to business rules) . The Handler is the place where all the domain rule checks are written and carried out on the command in accordance with the current state of aggregate root.

The Handler is a critical component of DDD Implementation not only from a business perspective but also from its technical characteristics.

The Handler implementation needs to be horizontal scalable , write consistent for all the commands issued for a persistent id and also quick in making decisions in terms of command executions. Write consistency can be achieved by doing cluster sharding of requests by entity ids.

Akka actors or Kafka consumers can be good candidates for Handlers.

Each Bounded Context is managed by a independent team

When we talk about providing a solution to a problem space , we think of who can provide the solution to it . Obviously, it’s a team :) . Now a team of developers and business analysts ? Yes. But business analysts who are experts in that domain and developers/architects who have tech expertise to code/design the domain.

Now one thing important as mentioned previously , the model , The Domain events , The Commands are domain centric and not tech centric . This means the developers code the solution based on how the domain behaves . Any change in the code is a change in the domain rules , or the domain model .(Unless if we change the programming language and re-write the entire code base , thats the different story .)

This means a business analyst needs to be involved to understand the change done in the code as the code change changes things from domain perspective.

This creative collaboration between domain experts and developers need to happen in a language understandable between both the parties . This language is called “ubiquitous language”.

A ubiquitous language is limited only to the bounded context . This means different bounded contexts have different language in which teams would communicate inside their bounded context .

This makes the team independent functioning unit per bounded context. Therefore the user subscription team will be different in its functioning and communication to the Book Borrowing team .

Communication between Bounded Contexts

So what are we going to learn in the next article ?

If you would have observed , we did not answer few questions in this article such as :

  • When a new copy was added for a book in the library , how do we ensure that the availability of the book is updated for the users to start booking ?
  • When a fine is overdue for a book borrowing how do we ensure , the fine creates a user subscription suspension and no book borrows requests are allowed for the user unless dues are cleared ?

These questions can be answered when the best practices around inter bounded context interactions are talked about.

First question would require the book domain to communicate with book borrowing domain and vice versa using book Id.

Second question would require the book borrowing domain to communicate with user subscription domain and vice versa using user Subscription Id .

Let’s talk about this in my next article when I introduce Read Sides , Domain Mapped Events , Event Sourcing and more.

Article Recommendations

https://dddcommunity.org/wp-content/uploads/files/pdf_articles/Vernon_2011_1.pdf. — Aggregate Design

--

--