By Dat Nguyen | Tech Lead, AnyTag Team
There are several possible approaches to architecture design, it becomes difficult to select the right one. However in order to solve complexity on business level, we have chosen Domain-Driven Design (or DDD in short).
Let’s first understand why we have selected DDD.
Firstly, DDD primarily concentrates on business problem and how to organize the logic that solves it. It’s a good candidate to deal with highly complicated domain or constantly evolving model.
Secondly, we found out that the first version of our system was hard to maintain and test, it was also hard to read from a functional perspective and hard to add more complex requirements. That was a reason why we decided to use Domain-Driven Design for the next step of our platform.
Next, let check some basic concept of DDD.
This layer presents the information to the client and interprets their actions.
This layer doesn’t contain business logic, its purpose only is to organize and delegate domain objects to do their job.
This is where concepts of the business, information about the business situation, and business rules are representing.
It implements all the technical functionalities the application needs to support the higher layers, persistence for the, messaging, communication between layers.
Note that not all layers are required in every system, but the existence of the Domain Layer is a prerequisite in DDD.
Other terms in Domain-Driven Design
An Entity is an object defined primarily by its identity, it’s also a combination of data and behavior.
# Entity example class Advertiser: id: AdvertiserId name: str def update(self, name: str) -> 'Advertiser': return replace(self, name = name)
The domain service is an additional layer that also contains domain logic. It’s a part of the domain model, just like entities.
# Domain services example def delete_advertiser(adv_repo: AdvertiserRepository, id: AdvertiserId) -> None: # do service stuff return
Repositories provide an interface that the Domain Layer can use to retrieve and persist objects.
# Repository interface example class AdvertiserRepository(ABC): @abstractmethod def update(self, advertiser: Advertiser) -> None: pass @abstractmethod def find_by_id(self, id: AdvertiserId) -> Optional[Advertiser]: pass
# Repository implementation example class AdvertiserRepositoryImpl(AdvertiserRepository): def update(self, advertiser: Advertiser) -> None: # implementation def find_by_id(self, id: AdvertiserId) -> Optional[Advertiser]: # implementation
Note that Repository interfaces are declared in the Domain Layer, but the repositories themselves are implemented in the Infrastructure Layer. This makes it easy to switch between different implementations of a repository without impacting any business code.
Lastly, although the domain-driven approach is able to solve the complexity challenge of software development, it has some disadvantages we need to think about before starting using it:
- – Requires Domain Experts
- – Requires additional efforts
- – Only Suitable for Complex Applications