How did I build the API using DDD?

Tram Ho

Following part 1 about DDD, in this part 2 I would like to introduce to you the commonly used architectures with DDD as well as other concepts surrounding the domain layer in DDD, hope you will receive it warmly.

Architectures commonly used with DDD

When applying the idea of ​​DDD to system design, we will often encounter the following common architectural styles:

  • 3-layer architecture (3 layers architecture).
  • Layered architecture.
  • Onion architecture.
  • Hexagonal architecture (Hexagonal architecture – Port and Adapter architecture).
  • Clean architecture.

We will go through the above architectural types in turn as overview and summary as possible.

3-layer architecture (3 layers architecture)

Outline of its shape:

Screen Shot 2023-06-02 at 7 56 43

This is a common architecture used by many web systems, in addition to being popular, its implementation is not too difficult, but it has major disadvantages as follows:

  • The layers will be very binding and interdependent, making it difficult to maintain or refresh each floor.
  • Linkage to a business logic class is very low.

To illustrate the above, let’s look at the following example:

Suppose we have a task management system with the following basic functions:

  • User cannot register multiple emails at the same time.
  • Newly created User will have a status of USING but can also be converted to SUSPENDING .
  • Tasks can only be assigned to users whose status is USING .
  • Task has only 2 states, COMPLETE or DOING .

Its use-case diagram will look like this:

Screen Shot 2023-06-04 at 16 43 41

Obviously, we see that the 2 domain knowledges knowledge User and Task are completely unrelated, so writing them in the same business logic layer will reduce the connection.

Briefly about association , this is a metric to evaluate the “quality” of a class. We take the following example:

We see that the greet method has absolutely nothing to do with the counter property of the class, moreover, the name of the class OperationUntil is also a very generic name that is not clear in meaning. Therefore method and property - thuộc tính of the class have NO RELATIONSHIP to each other. So this OperationUntil class can be considered as low coherence.

getCurrentCount and count are now related to each other and also to the Counter class, so we can conclude that this class is highly interconnected.

Going back to User and Task example above, it is completely unreasonable for us to implement domain logic at the Data Access layer because this layer will take on two tasks:

  • Implement business logic (model)
  • Perform DB (table) related processing

So it will make the two layers business logic and data access have a certain bond with each other.

ràng buộc here shows the interdependence between layers, classes

Let’s take for example:

Obviously, the print method of the Printer class depends on the count property of the Counter class.

We see that passing parameters to the print method of the Printer class reduces the interdependence of the Printer and Counter classes.

Layered architecture

Screen Shot 2023-06-04 at 21 30 24

In this architecture, we will separate the Bussiness Logic Layer Layer into 2 layers:

  • Application Layer – implement usecase
  • Domain Layer – implement domain logic

In this architecture:

  • The connection between floors was higher.
  • The dependency between layers has been reduced but the domain layer still depends on the infra layer (depending on using DB or OR Mapper) and this is something to avoid

Onion architecture

It is similar to the layered architecture above, but the dependence of the domain layer on the infra layer has been completely eliminated

Screen Shot 2023-06-04 at 21 36 34

Specifically, the domain layer will define the Repository Interface , the infra layer will “implement” the above-mentioned interfaces. So now the infra layer will depend on the domain layer. The infra layer will also save Domain Aggregate into the DB.

The characteristics of the remaining layers (Presentation, Usecase, Domain) will be as follows:

  • Domain layer: need to be independent, not dependent on any layer. Contains Value-Object , Domain Aggregate , Domain Event
  • Usecase layer: use public methods of Domain Aggregate , this layer is not allowed to depend on presentation layer
  • Presentation layer: interacts directly with the client, it contains classes: Controller , External-Controller

Onion Architecture

The access of external Actors to the Application as well as receiving req from the Application is through the Adapters.

Hexagonal architecture – Port and Adapter architecture

The main idea here is that the Application will interact with the outside world through:

  • Adapter
  • Dedicated port

The summary of this architecture will be as follows:

Hexagonal Architecture

Clean architecture

Clean Architecture

This architecture is the synthesis and inheritance from Onion Architecture and Hexagonal Architecture .

The naming of each floor may be slightly different:

  • Usecase Layer → Application layer
  • Adapter Layer → Interface Adapter Layer

Concepts around the domain layer

In addition to the concept of domain aggregate as mentioned in the previous section, we also have other concepts at the domain layer such as:

  • Value-Object
  • Domain Services
  • Repository
  • Factory

Value Object

Value-Object is a value type that makes no sense when not tied to any particular Aggregate. Let’s take for example:

In the company, each employee will have his own employee number (eg 1234), and this number will only be associated with a single employee. This code will be used to:

  • Work progress management
  • Human resources management

This means that the number 1234 has a very important meaning for the company and the employee itself. However, if it is not associated with any employee, it is simply a number , and it does not represent any business logic.

In my API I have defined a class EmailValueObject as follows: https://github.com/tuananhhedspibk/NewAnigram-BE-DDD-Public/blob/main/src/domain/value-object/email-vo.ts

With regular email, just following the correct format xxx@domain is enough, but maybe due to the specifics of the system, you can add other conditions for the email (eg: must start with a lowercase letter, .. .), the above conditions are business logic belonging to the domain layer.

In my API, I specify that the email must conform to a format checked by the predefined regex as follows:

Domain Services

Used when representing the model with an object is not possible . Usually will work with a set of objects .

For example, a typical example is checking whether mail is duplicated or not – in other words, mail has been used for users in the system or not. A user object itself can know its own mail but cannot know information about another object’s mail, so it is impossible to check it yourself.

Such cases will usually be handled by domain service .

But:

Try to use entity and value-object as much as possible and minimize the use of domain-service

The reason is because if you accidentally write a lot of business logic here, in the future it will become an unwanted Fat class.

Repository

Used to save Aggregate ‘s data to the DB. Usually 1 aggregate corresponds to a Repository.

Passing the aggregate to the repository or returning the aggregate must go through root aggregate . In my API, I create UserRepository as follows:

First, I define an abstract class IUserRepository at the domain layer.

It defines two method signatures, getByEmail & save , with functions respectively:

  • getByEmail : get user from DB via email.
  • save : save the user’s data to the DB.

I implement this abstract class at the infra layer as follows:

You can refer to the full source code of abstract class here and implement class here .

The reason I use abstract class and not interface here is: I want IUserRepository at the domain level to be like a BaseClass of the domain user so that I can create more SubBaseClass of other domain users like IAdminRepository or IMemberRepository .

Factory

Often used to create new objects when the logic of creating objects is quite complex. The factory itself is also considered a kind of domain service .

I take an example with the API I developed as follows:

Here I use the createUserEntity method to create UserEntities for the domain layer, with the input parameter being user with data type User – this data type has the same structure as User Model or in other words it is data data that I pull directly from the DB.

In the createUserEntity method, I use the plainToClass function of the class-transformer library with the purpose of “forcing” User Model to become UserEntity to get the output I want.

End of part two

So in this second part, I have presented to readers the commonly used architectural styles with DDD that are:

  • 3-layer architecture (3 layers architecture).
  • Layered architecture.
  • Onion architecture.
  • Hexagonal architecture (Port and Adapter architecture).
  • Clean architecture.

as well as other important concepts often mentioned in the domain layer of DDD such as:

  • Value-Object
  • Domain Services
  • Repository
  • Factory

Due to the limited knowledge and scope of the blog, I can only present to you the most important and summary content, hopefully they will be more or less useful for you later. Thank you very much for reading and see you in the third part of the series of blogs about DDD.

Share the news now

Source : Viblo