01 logo

Domain Driven Design — Domain Model and its Components

Domain Model and its Components

By Harish SubramaniamPublished 4 years ago 14 min read
3

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.

Domain Discovery - Library Book Borrowing

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 :

    1. whether the User has successfully on boarded our library system — User Domain
    2. whether the user is disqualified to be part of the system — User Domain
    3. whether the subscription of the user is suspended or not — Subscription Domain
    4. 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

    Bounded Context , Domain Model design - Library

    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

    how to
    3

    About the Creator

    Harish Subramaniam

    Passionate about software and help teach technology in a simple way .

    Follow me on twitter :

    @harish911

    Follow me on LinkedIn:

    https://www.linkedin.com/in/harish-subramaniam-a9493715/

    Reader insights

    Be the first to share your insights about this piece.

    How does it work?

    Add your insights

    Comments

    There are no comments for this story

    Be the first to respond and start the conversation.

    Sign in to comment

      Find us on social media

      Miscellaneous links

      • Explore
      • Contact
      • Privacy Policy
      • Terms of Use
      • Support

      © 2024 Creatd, Inc. All Rights Reserved.