DEV Community

Cover image for Applying Domain-Driven Design in Developing Software
Dantis Mai
Dantis Mai

Posted on • Updated on

Applying Domain-Driven Design in Developing Software

Domain-Driven Design (DDD) is a concept of developing software that the structure of the codebase deeply matched the business domain.

For more information about DDD, you can find it here

It took me pretty much time to understand and apply it to daily tasks, so this article is my sharing all I know about DDD.



That rule specifies that something declared in an outer circle >must not be mentioned in the code by an inner circle.
--- The Clean Architecture - Uncle Bob ---

Dependency is the only thing we can't avoid in software development because a component/class either uses another component/class or is being used by another one. But we can control the direction of dependency.

Let say we have a chain A -> B -> C

  • A depends on B, B depends on C, then C depends on A. So if C changes, B has to change, which leads to A has to change. We call this circular dependency, which is always a bad idea.
  • We can control this direction of dependency by Dependency Inversion. Adding additional component A', we have the new chain A -> B -> C, C -> A', A -> A'. Now, everything depends on A'. In this case, C may change, A may change, but when A' is the same, the changes won't affect others.
  • A' could be an interface in the context of Java language, and domain at the module level.

Layers role


  • Description: represent the entry point of requests, messages from SQS, SNS, or Kafka
  • Roles:
    • Middleware (Request validation, error handler).
    • Router.
    • Build response.


  • Description: call request to external resources like external APIs.
  • Roles:
    • Middleware (Request validation, authentication) for external API.
    • No business logic here.


  • Description: define resources replaced to infrastructure, like Database, Queues.
  • Roles:
    • Establish connection.
    • No business logic here.


  • Description: contain orchestration/process logic.
  • Roles:
    • Handle business flow.
    • Make external calls to retrieve data.


  • Description: contain shared logic across process, mainly focus on a specific domain.
  • Roles:
    • validation for business logic.
    • implement business logic.

Sometime, to simplify the codebase structure, We can combine infrastructure to repository, I do it in my example below.

When we talk about DDD, we talk about interfaces, and all of them are declared in the domain layer for each specific domain.
It's too much for theory, now come back to reality!!

How to apply it to project

Let assume we have a business flow below, which is about booking a seat in a cinema:

  • IsInvestor: We don't want to cause any trouble for investors, so if they ask for a seat, we will take it for them.
  • IsEligible: in this step, we will check whether the user's age is old enough.
  • Register seat: register the user for the seat.
  • Notification: send email to let them know whether the request is success or fail.


Now we structure our codebase from outer layer to inner layer based on the onion model above.

  • Controller: This layer handles 3 things

    • Mapping router to the controller.
    • Validate required data for the request.
    • Get suitable business flow in the Application layer to handle this request
  • Repository: This layer makes requests to external services. In this case, we may need user information from User downstream, seat information from Seat database.

  • Application: this layer just handles business logic of a specific domain, otherwise the Domain layer will do the job.

  • Domain: this layer handles business logic about User, Seat, and Email service. There is a lot of work to do here:

    • Retrieve required data for business logic from downstream.
    • Return result of isInvestor, isEligible, and so on.

After all all codebase will look like:

├── controller
|  └── register-seat
|     ├── registerSeat.controller.ts
|     ├── registerSeat.router.ts
|     └── registerSeat.validator.ts
├── application
|  └── register-seat
|     └── registerSeatService.ts
├── repository
|  └── email
|  |  ├── type.ts
|  |  ├── emailBuilder.ts
|  |  └── emailService.ts
|  ├── user
|  |  ├── type.ts
|  |  └── userService.ts
|  └── seat
|     ├── type.ts
|     ├── seatSchema.ts
|     └── seatService.ts
└── domain
   ├── user
   |  └── eligibility
   |     ├── isSeatAvailable.ts
   |     └── isCurrentUserOldEnough.ts
   ├── email
   |  └── sendEmail.ts
   └── seat
      └── seatInfoProvider.ts
Enter fullscreen mode Exit fullscreen mode

Note: sub-domains name may have different of mine, depended on project business domain.

As we can see in the onion model, the registry is the most outer layer, and the domain is the most inner one. How on earth the domain will call repositories to integrate with outside resources, so that violates our rule, right?

If we stay still applying the same thing for domain and repository, the answer for that question is yes 😑, we definitely break out the golden rule. It is the place which Dependency Inversion plays its role. After creating an interface for a repository, we inject that service into domain logic. So that, we separate 2 of them.x


This article is all thing I know about DDD, I may misunderstand some points, please help me fix it by leaving your feedback, I am really happy. Thanks for your precious time reading this.

Top comments (1)

pierre profile image
Pierre-Henry Soria ✨

Thanks for sharing those great pieces of advice about DDD, Dantis!