By Dat Nguyen | Tech Lead, AnyTag Team
In this blog, we are going to explore the approach which we have been using to build AnyTag & AnyCreator platforms.
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.
Layered Architecture
User Interface
This layer presents the information to the client and interprets their actions.
Application layer
This layer doesn’t contain business logic, its purpose only is to organize and delegate domain objects to do their job.
Domain layer
This is where concepts of the business, information about the business situation, and business rules are representing.
Infrastructure Layer
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
Entities
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)
Domain services
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
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