How did I build an API with DDD ? — Part 3

tuananhhedspibk
5 min readFeb 23, 2024

--

Welcome back to part 3 of my DDD series. You can review:

In this part, I would like to present two main contents as follows:

  1. Context division
  2. The architecture of the usecase layer

Context Division

About context division, a very simple idea that anyone can think of is one context — one application, which implies implementing micro-service.

This approach has scalability advantages as the system grows larger. However, it also incurs higher costs and can be challenging to deploy.

1 context — 1 application

Let’s consider an e-commerce application. Essentially, we can divide this application into 2 contexts:

  1. Seller: serving the sales process.
  2. Logistics: serving inventory management as well as goods handling.

With these two contexts, we can see that for the same product, each context will be concerned with different aspects.

For the Seller, it would be the price and the stock count of the items in the warehouse.

For Logistics, it would be the shipping status and the shipping address.

The system architecture with 2 contexts can be described simply as follows:

Basically, the interaction between the two contexts here is:

  • Synchronous: through direct calls like REST API calls.
  • Asynchronous: through sending events such as AWS SQS.

n context — 1 application

We will proceed to divide the folders corresponding to the contexts as shown in the example below:

app
--- delivery
------- domain
------- infra
------- repo

--- sale
------- domain
------- infra
------- repo

One thing to note here is that it’s essential to carefully divide the folders from the beginning to facilitate easy splitting of the app later when it grows larger.

The architecture of the use-case layer

The main purpose of the use-case layer

As its name suggests, the use-case layer itself is responsible for executing the core business functionalities of the system. I’ll give an example with my API, specifically the main functionalities on the user side, including:

  • Follow
  • Unfollow
  • Update Password
  • Update Profile

In that case, my use-case layer will include the main use cases as depicted below:

You can reference here

Let’s analyze the specific use case “UpdateProfile” — source code

This is a small snippet of code within the UpdateProfile use-case. We can see that the use-case is where we utilize domain entities, which can interact with each other, but it can also involve using predefined public methods (these methods will accurately reflect the business logic that the User Domain is responsible for) to execute a feature within the system — in this case, the UpdateProfile feature or updating user information.

Using methods of domain entities will increase the abstraction level for the domain layer since the use-case layer only cares about using methods without needing to concern itself with how they are implemented or what their internal contents are.

The return value from the use-case layer

As analyzed in part 2, if we use the Onion Architecture, our system can be diagrammed as follows:

We can see that the presentation layer will wrap around the use-case layer, so it will receive the return value from the use-case layer.

When it comes to passing the return value from the use-case layer down to the presentation layer, we have two approaches:

  1. Creating a dedicated class to hold this return data type.
  2. Passing the entire domain object down to the presentation layer.

Let’s analyze the advantages and disadvantages of these two approaches. For approach 1, its advantages can be listed as follows:

  • The presentation layer will not be able to see the domain business methods of the domain layer.
  • Avoiding modifications to the domain layer that could affect the presentation layer.

In my API, I use this approach. Returning to the example of the UpdateProfile use case above, I define a separate output class for this use case as follows:

You can reference here for more details.

The output in this use case will simply be to return the result of executing the use case, either success or failure, through the attribute code: ApiResultCode, very simply as follows:

The advantage is as stated, but the disadvantage of this approach is that the cost of data type conversion will increase.

With approach 2, its pros and cons are exactly opposite to those of approach 1.

If we execute approach 2, the domain object needs to have methods to serve the presentation, which will make the domain object bloated and difficult to maintain. Therefore, if we apply approach 2, instead of spending effort on data conversion, the price to pay will be equally high.

Assign name for Return Value Class

Bạn có thể đặt tên theo format XXXDTO — DTO là Data Transfer Object

You can assign in format XXXDTODTO is the short form ofData Transfer Object

Some other considerations when implementing the use-case layer

Dividing classes in the use-case layer

Usually, it’s one class per public method. In my API, I typically structure my use-case classes as follows:

You can reference here for more details.

Naming conventions for use-case classes

  1. For classes with only one method, we keep the verb intact — e.g., CreateTaskUseCase.
  2. For classes with multiple methods, we omit the verb from the class name — e.g., TaskUseCase.

In essence, naming use-case classes will depend on the content and business of each individual project. However, one thing to note here is that we should think of names for use-case classes from the stage of designing the use-case diagram.

Summary

So, part 3 of my DDD series has come to a close. In this part, I’ve shared with you about:

  1. Diving context
  2. The architecture of use-case layer

Thanks for reading and see you again in the next part of series about DDD.

--

--

tuananhhedspibk

Senior Software Engineer. Like building a system from scratch. Currently, have interested in System Design. DDD / Javascript / Typescript / React