DEV Community

Cover image for Domain-driven Design (DDD): File Structure
Steve Cruz
Steve Cruz

Posted on • Edited on • Originally published at Medium

Domain-driven Design (DDD): File Structure

Project Current File Structure

src folder
 -config folder
 -database folder
 -errors folder
 -middlewares folder
 -models folder
 -repositories folder
 -routes folder
 -services folder

The src folder should contain our application modules. Everything in it impacts directly how our application functions.

We currently separate our application by file types. So files of the service type are stored in the services folder.

The problem with this is: if we had 10 models, we would have around 40–50 services. Having so many files that do not deal with the same module, or in other words, do not deal with the same domain becomes confusing.

Domain

Is the sphere of knowledge that involves that particular file or module. A set of business rules.

So it is not always good to build our project structure around separating files by their type.

A good way to build our project structure is to isolate things according to their domain, by using modules.

So services related to users such as Authenticate User Service, Create Appointment Service, Create User Service, Update User Avatar Service should all belong to the User Domain.

There would be an Appointment Domain that should only take care of business logic related to appointments, be it to create appointments, list appointments, check available appointments or cancel appointments.

DDD can divide an ecommerce in the the delivery context with order, product and customer and the finance context.

DDD (Domain-driven Design)

Scrum is an agile methodology that allows the team to organize and execute their tasks in an agile manner.

But Scrum does not work in the same way for all teams and projects, we implement the ideas and concepts in the way that makes more sense for our context. DDD works in the same way, there are some concepts that only make sense in enterprise applications (very big applications).

Modules

What are the modules (spheres of knowledge) that we have in our application? The user domain and the appointment domain.
We must create a modules folder to accommodate our modules and inside it we create the users and appointments subfolders.

What can we store inside the modules/users folder?

  • DTOs
  • Entities/Models
  • Repositories (including our fake/mock implementations of them for TDD), services
  • Providers that are specific to users (a hash provider that hashes the user password)
  • Infra that is specific to users (we will see below what the infrastructure layer means).

Shared

Files with logic that is shared between all modules or multiple modules should be saved in the shared folder that is outside of the modules folder.

What can we store inside the shared folder?

  • Errors
  • Shared database files(connections and migrations)
  • Shared routes
  • Shared middleware
  • Shared providers (storage provider, email provider).

Infrastructure Layer vs Domain

Domain: Is the sphere of knowledge that involves that particular module. A set of business rules. It takes care of how the application should work.

Infrastructure Layer: Is how data that is initially held by in the domain's entities (memory) is persisted in databases or another persistent store. the infrastructure layer must not "contaminate" the domain model layer. 
NOTE: It contains our application's technical decisions

When creating eBarber a meeting with the barbers or the users will help you better determine the Domain/Business Logic. But a barber does not have the expertise to tell you what tools you should use: if you should use an ORM or a Query Builder.

A CTO and/or senior engineers decide things that are related to the infrastructure layer.

The user domain knows that when a user signs up he should receive an email, but it does not know what tool is being used to send emails. That is the role of the Infrastructure Layer.

The domain layer (service, repository, domain object) should not depend on the infrastructure layer nor the application layer

Read: Microsoft & DDD-oriented Microservices

Infra

To accommodate the infrastructure layer we can create multiple infra folders. We can have an infra folder inside our shared folder and inside each of our modules (users and appointments).

We can create a database folder inside the infra folder to deal with things related to our database. We should give it a specific name, since our project uses TypeORM, we should name our database folder as typeorm.

We can also create a http folder inside the infra folder to deal with things related to our http requests and responses such as server.ts, routes and middleware.
NOTE: Once again we should give it a specific name. If we were using another type of protocol such as gRPC we would give it another name.

Additional Notes

  1. Infra will store all information that is responsible for a specific package or library. Example: TypeORM.
  2. Errors may be shared but they are not part of the infra layer.
  3. It is good to include things that could change in the infra folder.
    • Example 1: Express routes and middleware.
    • Example 2: Imagine if we need to change the communication protocol from HTTP to gRPC.

Project File Structure using DDD

Alt Text

Alt Text

Alt Text

Final considerations

The main advantage of Domain Driven Design is being able to create code with well-defined components that have clear contracts between them. This allows us to better define their responsibilities, makes updating or replacing one of these components much easier with less impact on the overall system.

The key disadvantage is that DDD assumes that you have a fairly clear picture of the solution you are trying to produce, but that is not always the case. A solution to this problem is to create a small prototype, possibly iterate over it multiple times until you have enough understanding to come up with the reasonable design. This can delay the start of the project, but is likely to result in a more maintainable solution.

This post only touches the tip of the iceberg, I encourage you to read more about DDD including about the application layer.

Keep in touch

Contact me through my social media. Let's talk about DDD, TDD and good practices, be it on LinkedIn or GitHub.

Share with us how you decide your project file structure.

Top comments (14)

Collapse
 
alexeyzimarev profile image
Alexey Zimarev

Good content, but I feel I must add something.

  • DDD is not an architecture
  • DDD applies to software, both backend and frontend
  • It is about modelling the problem space in the solution space
  • It is about finding context boundaries using linguistic and business differences between contexts
  • It is also about being able to model each context using its own model with its own language

I totally agree that splitting files per type makes little sense. Developers deliver functionality and working on a certain feature should not require adding or changing files in twenty folders.

Concerning the infrastructure and dependencies, it, in fact, has little to do with DDD. Clean Architecture, ports and adapters, onion architecture and, in fact, software engineering is all about finding the right level of abstraction, building loosely-coupled and highly cohesive components.

Collapse
 
stevescruz profile image
Steve Cruz

Thank you for your contribution. It made me realize that I should emphasize more on determining context boundaries. That's another thing I like about DDD, the fact that it prioritizes using a set of language that translate business terms and complex ideas instead of overly technical language.

I especially like this quote I saw at Martin Fowler's website: "Domain experts should object to terms or structures that are awkward or inadequate to convey domain understanding; developers should watch for ambiguity or inconsistency that will trip up design."

Your last paragraph was perfect.

Collapse
 
aleron75 profile image
Alessandro Ronchi

Thanks for sharing your point of view on how to apply DDD in real projects, very useful reading.

I've also found it very inspiring to read this book by Matthias Noback:

matthiasnoback.nl/book/advanced-we...

The book goes deep into the idea of separating core (domain) code from infrastructure, with a lot of real code examples.

It uses PHP examples but the concepts can be applied in general.

Collapse
 
goceb profile image
Goce • Edited

+1 for the Matthias Noback book, although I do not agree with everything in the book (personal preferences etc..), still it is a good read especially if you are just starting out with getting some order in your code..
@stevescruz the folder structure presented seems like it fits just one bounded context and at a larger scale it will get messy. Something that has been in use for quite some time in the DDD community is the Application/Domain/Infrastructure groupings by BC. That way you have clear separation between them and if you are starting with a monolith it is quite easy to move to microservices since the code is already logically grouped across your future service boundaries. Here is a quick example of what a single folder looks like:

Folders

Collapse
 
delucca profile image
Daniel De Lucca • Edited

Sorry for reviving this thread, but I've just found out your suggested DDD folder structure and it is pretty interesting!

I was separating application, domain and infrastructure inside my src folder, but your approach is way cleaner.

I just have a simple question, where do you store abstract interfaces? For example, I have a bunch of abstract classes that I use inside all my layers, in which folder would you store those?

Just to be clear, those abstractions are used by basically all my aggregates. For example, I have an "DomainAggregateService" abstract class, which is basically some global methods that every aggregate service has.

I'm thinking to give it a shot to your suggestion, and placing it in something like lib/... or something like that (while my aggregates would be inside src, since I'm using Javascript and it is pretty common to place source code inside src folder)

Thread Thread
 
goceb profile image
Goce

Abstract classes like AggregateRoot, AggregateRootCollection and interfaces like DomainEvent / Listeners etc are defined in a composer package that is imported in the project.

Collapse
 
stevescruz profile image
Steve Cruz

You are right, I can view more clearly the domain and the infrastructure groups, but sometimes I lose sight of the application group.

Thanks for sharing this file structure, it is very organized. I love seeing how other people structure things.
Where do you store your routes (I imagine in the web folder at infrastructure)? and your controllers and why?

Thread Thread
 
goceb profile image
Goce

Controllers are part of the infrastructure layer so, Infrastructure/Web is where all the web related stuff sits. Console is for console commands (still in Infrastructure). Routes on the other hand exist in an independent file just for routes (not a big fan of annotations), dependencies are defined in a separate file too, which aggregates the different dependency providers for each BC or module. I use PHP (no framework) but I think this approach is language agnostic.

Collapse
 
stevescruz profile image
Steve Cruz

I did not know this book. Thanks for sharing Alessandro, I'll definitely check it out.

Collapse
 
bias profile image
Tobias Nickel

I never did DDD, in my team we also.have the separation by filetype. but I tell my team they should develop the api in a way, that we could swop from graphql to http, socket or other protocol. That leads to have very small resolver/handler.

Do you have some opinion and thought using DDD in a microservice or monolithic architecture?

Collapse
 
stevescruz profile image
Steve Cruz • Edited

I do not have experience with monolithic architecture, only with microservices. I'd recommend using DDD in a microservice with your use case.

In my situation it allowed me to change from MongoDB to PostgreSQL without a hassle since the infrastructure layer was separate from the domain.

I use dependency injection to inject the repository implementation (infrastructure layer that is related to the database) onto my repositories and services (both part of the domain layer), so when I needed to change from MongoDB to PostgreSQL it was only necessary to change things in the infrastructure layer such as the repository implementation and some other minimal things like the database connection. In the end the repository and the services were unchanged.

So I strongly recommend you to use DDD with Dependency Injection and Liskov Substitution principle.

Show this to your colleagues: docs.microsoft.com/en-us/dotnet/ar...

Collapse
 
ccunnin297 profile image
Cole Cunningham • Edited

For a monolith, it's especially useful, as it makes it easier to find what you're looking for when you have a lot of files.

For swapping between different third party libraries or protocols, Steve's answer is on point. Splitting infrastructure from the domain with a common interface / adapter will allow you to swap the inner workings of infrastructure without having to rip apart your domain layer.

Collapse
 
stevescruz profile image
Steve Cruz

That makes sense, because there are many massive sized monolithic codebases, so it's imperative to structure it in a manner to find things easier. Thanks for the tip Cole.

Collapse
 
ajoshi31 profile image
Atul Joshi • Edited

@stevescruz Here modules act as bounded context or same part of domain.

As I see in CreateAppointmentService

import IAppointmentsRepository from '@modules/appointments/repositories/IAppointmentsRepository';
import INotificationsRepository from '@modules/notifications/repositories/INotificationsRepository';

Is it ok to call different modules which act as the cross boundaries? I need to know without event driven how can we make cross boundary communication. Is it ok to duplicate code/entity for each domains/sub-domains?