Implementing Domain-Driven Design is a software design approach. How do you start to design any software? A complex problem can be overwhelming. Even if you want to look at the existing code base and figure out the design, it can be a lot of work. As you build, the distributed system can get complex. This post is part of Distributed System Design.
The domain-driven approach of software development works in sync with domain experts. Usually, one would discuss the problem with domain experts to figure out what domains and rules can be created and how they are changed in the application. Object-oriented design is nothing but domain-driven design. Domains are objects. Irrespective of what language you choose, you need to create domain objects.
Discussing with domain experts
A complex problem needs a discussion with domain experts. Once you have collected all the information about rules and conditions, you can start representing the real-life problem in a domain object. Rules and conditions can help on how to represent the domain and how the domain would interact with other domain objects.
The first step is to choose domain model names. Again, this depends on your domain.
If we have to take an example of a local library, we will have domain objects like Book
, Author
, User
and Address
. A user of the library borrows the book of a particular author from the library. Of course, you can also add Genre
. Either you would talk to a local library and see what they need to build an online system to track their books inventory. This discussion will give you an idea about what your end-user wants and how the user wants to use the system.
Basic Building Blocks
Once we have enough information about the domain, we create basic building blocks. The reason to start with domain-driven design as basic building blocks is that this part of the design is least changeable. Over the course of the application lifespan, this will not change. That’s why it is important to build this as accurately as possible.
Entities
Entities are domain objects. We identify these domain objects uniquely. A general way to identify these objects is to create a field id
which can be of type UUID.
As discussed in our example for building an online library system to manage books, Book
, Author
, Genre
will be different entities and We will add a field id
in each of these entities to identify them uniquely.
public class Book
{
private UUID id;
private String title;
private String isbn;
private Date created;
private Date updated;
}
Value Objects
Value objects are attributes or properties of entities. Like above where we created Book
entity, title
, isbn
are value objects of this entity.
Repositories
Nevertheless, repositories are an intermediate layer between Services that need to access domain object data from the persistence technologies like a database.
Aggregates
Aggregates are a collection of entities. This collection is bound together by a root entity. Entities within aggregates have a local identity, but outside that border, they have no identity.
Services
Services drive the domain-driven design. They are the doer of the entire system. All your business logic relies on services. When you get a request to fetch or insert data, services carry out the validation of rules and data with the help of entities, repositories, and aggregates.
Factories
How do you create aggregates? Usually, factories provide help in creating aggregates. If an aggregate is simple enough, one can use aggregate’s constructor to create aggregate.
One of the ways to implement factories is to use Factory Pattern. We can also use an abstract factory pattern to build hierarchies of classes.
In conclusion, to understand all of this
- Aggregates – are made of entities.
- Aggregates – use services.
- Factories – create new aggregates.
- Repositories – retrieval, search, deletion of aggregates.
Understanding Bounded Context in Domain-Driven Design
The challenging part of any design is how to make sure that our efforts are not duplicated. If you create feature A with certain domains and another feature B that includes part of domains from feature A, then we are duplicating the efforts. That’s why it’s important to understand the bounded context when designing your application architecture. In such cases, one can easily use Liskov Substitution Principle.
The more features you add, the more complex the design gets. With increasing models, it is even harder to guess what you will need in the future. Overall, in such scenarios, you build a bounded context. The bounded context contains unrelated models but also shares the common models. This helps in dividing the complex and large models which can share some models.
Conclusion
In this post, we learned how to use domain-driven design to build a scalable system. If you want to learn more about this topic, you can get this course on domain-driven design.
Good news for readers – a Black Friday sale on my book Simplifying Spring Security till Nov 30th. Buy it here.