Playing football is very simple, but playing simple football is the hardest thing there is. — Johan Cruyff
If we take this famous quote for programming, we can say;
Writing code is very simple, but writing simple code is the
hardest thing there is.
Domain-driven design (DDD) is an approach to software
development for complex needs by connecting the implementation to an evolving model;
DDD is suitable for complex domains and large-scale applications rather than simple CRUD applications.
It focuses on the core domain logic rather than the infrastructure details. It helps to build a ﬂexible, modular and maintainable code base.
Implementing DDD highly relies on the Object Oriented
Programming (OOP) and SOLID principles.
Actually, it implements and extends these principles. So, a good understanding of OOP & SOLID helps you a lot while truly
implementing the DDD.
There are four fundamental layers of a Domain Driven
Business Logic places into two layers, the Domain layer and
the Application Layer, while they contain different kinds of
Domain Layer implements the core, use-case independent business logic of the domain/system.
Application Layer implements the use cases of the application based on the domain. A use case can be thought as a user interaction on the User Interface (UI).
Presentation Layer contains the UI elements (pages, components) of the application.
Infrastructure Layer supports other layer by implementing the abstractions and integrations to 3rd-party library and systems.
The same layering can be shown as the diagram below and known as the Clean Architecture, or sometimes the Onion Architecture:
DDD mostly focuses on the Domain & Application Layers and ignores the Presentation and Infrastructure. They are seen as details and the business layers should not depend on them.
That doesn't mean the Presentation and Infrastructure layers are not important.
They are very important. UI frameworks and database providers have their own rules and best practices that you need to know and apply. However these are not in the topics of DDD.
This section introduces the essential building blocks of the Domain & Application Layers.
● Entity: An Entity is an object with its own properties (state, data) and methods that implements the business logic that is executed on these properties. An entity is represented by its unique identiﬁer (Id). Two entity object with different Ids are considered as different entities.
● Value Object: A Value Object is another kind of domain object that is identiﬁed by its properties rather than a unique Id. That means two Value Objects with same properties are considered as the same object. Value objects are generally implemented as immutable and mostly are much simpler than the Entities.
● Aggregate & Aggregate Root: An Aggregate is a cluster of objects (entities and value objects) bound together by an Aggregate Root object. The Aggregate Root is a speciﬁc type of an entity with some additional responsibilities.
● Repository (interface): A Repository is a collection-like
interface that is used by the Domain and Application Layers to access to the data persistence system (the database). It hides the complexity of the DBMS from the business code. Domain Layer contains the interface s of the repositories.
● Domain Service: A Domain Service is a stateless service that implements core business rules of the domain. It is
useful to implement domain logic that depends on multiple aggregate (entity) type or some external services.
● Speciﬁcation: A Speciﬁcation is used to deﬁne named, reusable and combinable ﬁlters for entities and other business objects.
● Domain Event: A Domain Event is a way of informing other services in a loosely coupled manner, when a domain speciﬁc event occurs.
● Application Service: An Application Service is a stateless
service that implements use cases of the application. An application service typically gets and returns DTOs. It is used by the Presentation Layer. It uses and coordinates the domain objects to implement the use cases. A use case is typically considered as a Unit Of Work.
● Data Transfer Object (DTO): A DTO is a simple object without any business logic that is used to transfer state (data) between the Application and Presentation Layers.
● Unit of Work (UOW): A Unit of Work is an atomic work that should be done as a transaction unit. All the operations inside a UOW should be committed on success or rolled back on a failure.
The picture below shows a Visual Studio Solution created using the ABP's application startup template :
The Domain Layer is splitted into two projects;
- IssueTracking.Domain is the essential domain layer that contains all the building blocks (entities, value objects, domain services, speciﬁcations, repository interfaces, etc.) introduced before.
● IssueTracking.Domain.Shared is a thin project that contains some types those belong to the Domain Layer, but shared with all other layers. For example, it may contain some constants and enums related to the Domain Objects but need to be reused by other layers.
The Application Layer is also splitted into two projects;
IssueTracking.Application.Contracts contains the application service interfaces and the DTOs used by these interfaces. This project can be shared by the client applications (including the UI).
IssueTracking.Application is the essential application
layer that implements the interfaces deﬁned in the Contracts project.
- IssueTracking.Web is an ASP.NET Core MVC / Razor Pages application for this example. This is the only executable application that serves the application and the APIs.
IssueTracking.HttpApi project contains HTTP APIs deﬁned by the solution. It typically contains MVC Controllers and related models, if available. So, you write your HTTP APIs in this project.
IssueTracking.HttpApi.Client project is useful when you
have a C# application that needs to consume your HTTP APIs. Once the client application references this project, it can directly inject & use the Application Services. This is possible by the help of the ABP Framework's Dynamic C# Client API Proxies System.
In a DDD implementation, you may have a single Infrastructure
project to implement all the abstractions and integrations, or you may have different projects for each dependency.
We suggest a balanced approach; Create separate projects for
main infrastructure dependencies (like Entity Framework Core)
and a common infrastructure project for other infrastructure.
ABP's startup solution has two projects for the Entity
Framework Core integration;
● IssueTracking.EntityFrameworkCore is the essential integration package for the EF Core. Your application's DbContext, database mappings, implementations of the repositories and other EF Core related stuff are located here.
● IssueTracking.EntityFrameworkCore.DbMigrations is a special project to manage the Code First database migrations. There is a separate DbContext in this project to track the migrations. You typically don't touch this project much except you need to create a new database migration or add an application module that has some database tables and naturally requires to create a new database migration.
There is one more project, IssueTracking.DbMigrator, that is a simple Console Application that migrates the database schema and seeds the initial data when you execute it.
It is a useful utility application that you can use it in development as well as in production environment.
The ﬁgure below shows a typical request ﬂow for a web app that has been developed based on DDD patterns.
● The request typically begins with a user interaction on the UI (a use case) that causes an HTTP request to the server.
● An MVC Controller or a Razor Page Handler in the Presentation Layer (or in the Distributed Services Layer) handles the request and can perform some cross cutting concerns in this stage (Authorization, Validation, Exception Handling, etc.).
A Controller/Page injects the related Application Service interface and calls its method(s) by sending and receiving DTOs.
● The Application Service uses the Domain Objects (Entities, Repository interfaces, Domain Services, etc.) to implement the use case. Application Layer implements some cross cutting concerns (Authorization, Validation, etc.). An Application Service method should be a Unit Of Work. That means it should be atomic.
Most of the cross cutting concerns are automatically and conventionally implemented by the ABP Framework and you typically don't need to write code for them.
The domain and the application layers should be ORM/DB Provider agnostic.
They should only depend on the Repository interfaces and the Repository interfaces don't use any ORM speciﬁc objects.
Here, the main reasons of this principle;
To make your domain/application infrastructure independent since the infrastructure may change in the future or you may need to support a second database type later.
To make your domain/application focus on the business
code by hiding the infrastructure details behind the repos.
To make your automated tests easier since you can mock the repos in this case.