Implementing Domain Driven Design (Part 1)
A Practical Guide For Implementing The Domain Driven Design With The ABP Framework
Author: Halil İbrahim Kalkan
Designer: Melis Platin
Publish Date: June, 2021 (First Edition)
CONTENTS
Introduction.....................................................................
-
Goal...........................................................................
-
Simple Code ...................................................................
What is the Domain Driven Design?................................................
-
OOP & SOLID....................................................................
-
DDD Layers & Clean Architecture................................................
-
Core Building Blocks...........................................................
Implementation: The Big Picture..................................................
-
Layering of a .NET Solution....................................................
-
Dependencies of the Projects in the Solution ..................................
-
Execution Flow of a DDD Based Application .....................................
-
Common Principles .............................................................
Implementation: The Building Blocks .............................................
-
The Example Domain ............................................................
-
Aggregates ....................................................................
-
Repositories ..................................................................
-
Specifications ................................................................
-
Domain Services ...............................................................
-
Application Services ..........................................................
-
Data Transfer Objects .........................................................
Example Use Cases ...............................................................
-
Entity Creation ...............................................................
-
Updating / manipulating An Entity .............................................
Domain Logic & Application Logic.................................................
-
Multiple application Layers....................................................
-
Examples.......................................................................
Reference Books..................................................................
INTRODUCTION
This is a practical guide for implementing the Domain Driven Design (DDD). While the implementation details rely on the ABP Framework infrastructure, core concepts, principles and patterns are applicable in any kind of solution, even if it is not a .NET solution.
Goal
The goals of this book are:
-
Introduce and explain the DDD architecture, concepts, principles, partterns and build blocks.
-
Explain the layered architecture & solution structure offered by the ABP Framework.
-
Introduce explicit rules to implement DDD partterns and best practices by giving concrete examples.
-
Show what ABP Framework provides you as the infrastructure for implementing DDD in a proper way.
-
And finally, provide suggestions based on sofware development best practices and our experiences to create a maintainable codebase.
Simple Code!
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. ----???
In this document, we will introduce simple rules, those are easy to implement.
Once your application grows, it will be hard to follow these rules. Sometimes you find breaking rules will save you time in a short term. However, the saved time in the short term will bring much more time loss in the middle and long term. Your code base becomes complicated and hard to maintain. Most of the business applications are re-written just because you can't maintain it anymore.
If you follow the rules and best practices, your code base will be simpler and easier to maintain. Your application reacts to changes faster.
What is the Domain Driven Design?
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 flexible, modular and maintainable code base.
OOP && SOLID
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.
DDD Layers & Clean Architecture
There are four fundamental layers of a Domain Driven Based Solution.
Business Logic places into two layers, the Domain Layer and the Application Layer, while they contain different kinds of business logic:
-
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:
In the Clean Architecture, each layer only depends on the layer directly inside it. The most independent layer is shown in the most inner circle and it is the Domain Layer.
Core Building Blocks
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.
Domain Layer Building Blocks
-
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 identifier(Id). Two entity objects with different Ids are considered as different entities.
-
Value Object: A Value Object is another kind of domain object that is identified by its properties rather than a unique Id. That means two Vaue Objects with same properties are considered as the same objects. 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 specific 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 interfaces 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.
-
Specification: A Specification is used to define named, reusable and combinable filters 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 specific event occurs.
Application Layer Building Blocks
-
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.
Implementation: The Big Picture
Layering of a .NET Solution
The picture below shows a Visual Studio created using the ABP's application startup template:
The solution name is IssueTracking and it consists of multiple projects. The solution is layerd by considering DDD principles as well as development and deployment practicals. The sub sections below explains the projects in the solution;
Your solution structure may be slightly different if you choose a different UI or Database provider. Howerer, the Domain and Application layers will be same and this is the essential point for DDD perspective. See the Application Startup Template document if you want to know more about the solution structure.
The Domain Layer
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, specifications, reposiotry 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
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 and implements the interfaces defined in the Contracts project.
The Presentation Layer
- IssueTracking.Web is an ASP.NET MVC/Razor Pages application for this example. This is the only executable application that serves the application and the APIs.
ABP Framework also supports different kind of UI frameworks including Angular and Blazor. In these cases,the IssueTracking.Web doesn't exist in the solution. Instead, an fdsfs application will be in the solution to serve the HTTP APIs as a standalone endpoint to be consumed by the UI applications via HTTP API calls.
The Remote Service Layer
- IssueTracking.HttpApi project contains HTTP APIs defined by the solution. It typically contains MVC Controllers and related models, if available. So, you write your HTTP APIs in this project.
Most of the time, API Controllers are just wrappers around the Application Services to expose them to the remote clients. Since ABP Framework's Automatic API Controller System automaticlly configures and exposes your Application Services as API Controllers, you typically don't create Controllers in this project. However, the startup solution includes it for the cases you need to manually create API controllers.
- 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.
There is a Console Application in the test folder of the solution, named IssueTracking.HttpApi.Client.ConsoleTestApp. It simply uses the IssueTracking.HttpApi.Client project to consume the APIs exposed by the application. It is just a demo application and you can safely delete it. You can even delete the IssueTracking.HttpApi.Client project if you think that you don't need to them.
The Infrastructure Layer
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 apporach; 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 dont' touch this project much except you need to create a new database migration or add an application module that has some datebase tables and naturally requires to create a new database migration.
You may wonder why there are two projects for the EF Core. It is mostly related to modularity. Each module has its own independent DbContext and your application has also one DbContext. DbMigrations project contains a union of the mudules to track and apply a single migraton path. While most of the time you don't need to know it, you can see the EF Core migrations document for more information.
Other Projects
There is one more project, IssueTracking.DbMigrator, this 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.
Dependencies of the Projects in the Solution
The diagram below shows the essential dependencies (project references) between the projects in the solution (IssueTracking.part is not shown to be simple)
The projects have been explained before. Now, we can explain the reasons of the dependencies:
-
Domain.Shared is the project that all other projects directly or indirectly depend on. So, all the types in this project are available to all projects.
-
Domain only depends on the Domain.Shared because it is already a (shared) part of the domain. For example, an IssueType enum in the Domain.Shared can be used by an Issue entity in the Domain project.
-
Application.Contracts depends on the Domain.Shared. In this way, you can reuse these types in the DTOs. For example, the same IssueType enum in the Domain.Shared can be used by a CreateIssueDto as a property.
-
Application depends on the Application.Contracts since it implements the Application Service interfaces and uses the DTOs inside it. It also depends on the Domain since the Application Services are implemented using the Domain Objects defined inside it.
-
EntityFrameworkCore depends on the Domain since it maps the Domain Objects (entities and value types) to database tables (as it is an ORM) and implements the repository interfaces defined in the Domain.
-
HttpApi depends on the Application.Contracts since the Controllers inside it inject and use the Application Service interfaces as explained before.
-
HttpApi.Client depends on the Application.Contracts since it can consume the Application Services as explained before.
-
Web depends on the HttpApi since it serves the HTTP APIs defined inside it. Also, in this way, it indirectly depends on the Application.Contracts project to consume the Application Services in the Pages/Components.
Dashed Dependencies
When you investigate the solution, you will see two more dependencies shown with the dashed lines in the figure above. Web project depends on the Application and EntityFrameworkCore projects which theoretically should not be like that but actually it is.
This is because the Web is the final project that runs and hosts the application and the application needs the implementations of the Application Services and the Repositories while running.
This design decision potentially allows you to use Entities and EF Core objects in the Presentation Layer which should be strictly avoided. However, we find the altern designs over complicated. Here, two of the alternatives if you want to remove this dependency;
-
Convert Web project to a razor class library and create a new project, like Web.Host, that depends on the Web, Application and EntityFrameworkCore projects and hosts the application. You don't write any UI code here, but use only for hosting.
-
Remove Application and EntityFrameworkCore dependencies from the Web project and load their assemblies on application initialization. Your can use ABP's Plug-In Modules system for that purpose.
Execution Flow of a DDD Based Application
The figure below shows a typical request flow for a web application 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.
Common Principles
Before going into details, let's see some overall DDD principles;
Datebase Provider / ORM Independence
The domain and the application layers should be ORM/Database Provider agnostic. They should only depend on the Repository interfaces and the Repository interfaces don't use any ORM specific 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 repositories.
- To make your automated tests easier since you can mock the repositories in this case.
As a respect to this principle, none of the projects in the solution has reference to the EntityFrameworkCore project, except the startup application.
Discussion About the Database Independence Principle
Especially, the reason 1 deeply effects your domain object design (especially, the entity relations) and application code. Assume that you are using Entity Framework Core with a relational database. If you are willing to make your application switchable to MongoDB later, you can't use some very useful EF Core features.
Examples:
-
You can't assume Change Tracking since MongoDB provider can't do it. So, you always need to explictly update the
-
You can't use Navigation Properties (or Collections) to other Aggregates in your entities since this is not possible for a Document Database. See the "Rule: Reference Other Aggregates Only By Id" section for more info.
If you think such features are important for you and you will never stray from the EF Core, we believe that it is worth stretching this principle. We still suggest to use the repository pattern to hide the infrastructure details. But you can assume that you are using EF Core while designing your entity relations and writing your application code. You can even reference to the EF Core NuGet Package from your application layer to be able to directly use the asynchronous LINQ extension methods, like ToListAsync() (see the IQueryable & Async Operations section in the Repositories document for more info).
Presentation Technology Agnostic
The presentation technology (UI Framework) is one of the most changed parts of a real world application. It is very important to design the Domain and Application Layers to be completely unaware of the presentation technology/framework. This principle is relatively easy to implement and ABP's startup template makes it even easier.
In some cases, you may need to have duplicate logic in the application and presentation layer. For example, you may need to duplicate the validation and authorization checks in both layers. The checks in the UI layer is mostly for user experience while checks in the application and domain layers are for security and data integrity. That's perfectly normal and necessary.
Focus on the State Changes, Not Reporting
DDD focuses on how the domain objects changes and interactions; How to create an entity and change its properties by preserving the data integrity/validity and implementing the business rules.
DDD ignores reporting and mass querying. That doesn't mean they are not important. If your application doesn't have fancy
dashboards and reports, who would use it? However, reporting is another topic. You typically want to use the full power of the SQL Server or even use a separate data source (like ElasticSearch) for reporting purpose. You will write optimiazed queries, create indexes and even stored procedures! You are free to do all these things as long as you don't infect them into your business logic.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET10 - 预览版1新功能体验(一)