The ABP Framework project's main goal is to introduce an architectural approach to application development and provide the necessary infrastructure and tools to implement that architecture with best practices.
Domain-driven design (DDD) is one of the core parts of ABP Framework's architecture offering. ABP's startup templates are layered based on DDD principles and patterns. ABP's entity, repository, domain service, domain event, specification, and many other concepts are directly mapped to the tactical patterns of DDD.
Since DDD is a core part of the ABP application development architecture, this book has a dedicated section, Part 3, Implementing Domain-Driven Design, for DDD that consists of three chapters. In this book, I will focus on practical implementation details rather than the theoretical and strategic approaches and concepts of DDD. The examples will be mostly based on the EventHub project that was introduced in Chapter 4, Understanding the Reference Solution. In addition, I will show different examples for some scenarios that the EventHub project has no proper examples of.
The next two chapters will show you explicit rules and concrete code examples for implementing DDD to help you learn how to implement DDD with the ABP Framework.
However, in this first chapter, we will look at DDD in general and explore the core technical concepts in the following order:
You can clone or download the source code for the EventHub project from GitHub: https://github.com/volosoft/eventhub.
If you want to run the solution in your local development environment, you need to have an IDE/editor (such as Visual Studio) to build and run ASP.NET Core solutions. Also, if you want to create ABP solutions, you need to have the ABP CLI installed, as explained in Chapter 2, Getting Started with ABP Framework.
Before we cover the implementation details, let's define DDD's core concepts and building blocks. Let's begin with the definition of DDD.
DDD is a software development approach for complex needs where you connect the software's implementation to an evolving model.
DDD is suitable for complex domains and large-scale applications. In the case of simple, short-lived Create, Read, Update, Delete (CRUD) applications, you typically don't need to follow all the DDD principles. Fortunately, ABP doesn't force you to implement all the DDD principles in every application; you can just use which principles work best for your application. However, following DDD principles and patterns in a complex application helps you build a flexible, modular, and maintainable code base.
DDD focuses on core domain logic rather than the infrastructure details, which are generally isolated from the business code.
Implementing DDD is closely related to object-oriented programming (OOP) principles. This book doesn't cover these basic principles, but still, a good understanding of OOP and the single responsibility, open-closed, Liskov-substitution, interface segregation, and dependency inversion (SOLID) principles will help you a lot while you're shaping and organizing your code base and implementing DDD in practice.
Now that we've provided this brief definition, we can explore the fundamental layers of DDD.
Layering is a common principle of organizing software solutions to reduce complexity and increase reusability. DDD offers a four-layered model to help you organize your business logic and abstract the infrastructure from the business logic, as shown in the following diagram:
The preceding diagram shows the layers and their relationships:
Each layer of this model has a responsibility and contains various building blocks, which are introduced in the next section.
From a technical perspective, DDD is mostly related to designing your business code by focusing on the domain you are working on. Business logic is separated into two layers – the domain layer and the application layer. The other layers (presentation and infrastructure) are considered as implementation details and should be implemented based on the best practices of the specific technologies you are using, such as Entity Framework.
The domain layer implements the core domain logic by using the following fundamental building blocks:
The application layer implements the use case of the application by using the following building blocks:
It was important to see the big picture and become familiar with the core building blocks of DDD, which I why I introduced them in brief here. In the next few chapters, we will use them in practice and understand their implementation details. However, in this chapter, I will continue with the big picture and explain how ABP places the layers and building blocks into a .NET solution.
So far, we have been introduced to the layers and core building blocks of a DDD-based software solution. In this section, we will learn how a .NET solution can be layered based on DDD. I will begin with the simplest possible solution structure. Then, I will explain how ABP's startup solution template evolved into its current structure. Finally, you will understand why the ABP startup solution has that many projects inside it and the purpose of each.
Let's start from scratch and keep things simple by creating four projects in our .NET solution, as shown in the following screenshot:
Assuming that we are building a Customer Relationship Management (CRM) solution, Acme is our company name, and Crm is the product name in this example. I've created a separate C# project for each layer. .NET projects perfectly fit into layers as they can physically separate the code base into different packages. A class/type in a project can directly use other classes/types in the same project. However, a class/type can't use a class/type in another project unless you explicitly define the dependency by referencing the other project.
Figure 9.2 shows the projects in the solution in Visual Studio, as well as the dependencies between these projects:
In the preceding diagram, the solid lines represent development-time dependencies (project references), while the dashed line represents runtime dependencies. I will explain the difference later in this section.
To understand these dependencies, we need to know what type of components these projects may contain. We saw which components are located in the domain and application layers in the Building blocks section. Here, I will mention some example components that are included in the projects of that CRM solution:
Now that we understand the purpose and contents of these projects, let's see why the projects have these dependencies:
The Acme.Crm.Web project has one more dependency: it references the Acme.Crm.Infrastructure project. It doesn't directly use any class in that project, so there is no need for a direct dependency. However, Acme.Crm.Web is also the project that runs the application, and the application needs the infrastructure layer at runtime to use the database. An alternative structure will be discussed in the Separating the hosting from the UI section so that you can get rid of that dependency.
This was a minimalistic layering of a DDD-based solution. In the next section, we will use that solution and explain how ABP's startup solution has evolved.
ABP's startup solution is more complex than the solution shown in Figure 9.2. The following screenshot shows the same solution that was created with the ABP startup template, but this time using the abp new Acme.Crm CLI command:
Let's explain how this solution evolved from the four-project solution explained in the previous section.
The minimalistic DDD solution contains the Acme.Crm.Infrastructure project, which is assumed to implement all the infrastructural abstractions and integrations. An ABP solution, on the other hand, has a dedicated Entity Framework Core integration project (Acme.Crm.EntityFrameworkCore) since we think it is good to create separate projects for such major dependencies, especially for the database integration.
The infrastructure layer can be split into multiple projects. The ABP startup template has no such major dependency. The only infrastructure project is the Acme.Crm.EntityFrameworkCore project. If your solution grows, you can create additional infrastructure projects.
With this change, the initial minimalistic DDD-based solution will be as follows:
This change was trivial. It can be thought of as changing the Acme.Crm.Infrastructure project's name to Acme.Crm.EntityFrameworkCore. The next section will introduce a new project to the solution.
Currently, the Acme.Crm.Application project contains the application service classes. Therefore, the Acme.Crm.Web project references the Acme.Crm.Application project to use these services.
This design has a problem: the Acme.Crm.Web project indirectly references the Acme.Crm.Domain project (over the Acme.Crm.Application project). This exposes the business objects (such as entities, domain services, and repositories) in the domain layer to the presentation layer and breaks the abstraction and true layering.
The ABP startup template separates the application layer into two projects:
Introducing contracts (interfaces) for the application services has two important advantages:
The EventHub reference solution (introduced in Chapter 4, Understanding the Reference Solution) takes advantage of this design and reuses the Application.Contracts project between the UI and the HTTP API applications. This way, it easily sets up a tiered architecture where the application layer and the presentation layer are hosted in different applications yet share service contracts.
By separating the application contracts project, the current solution structure will look like the one in the following figure:
With this new design, the project dependency graph will be like in the following figure:
The Acme.Crm.Web project now only depends on the Acme.Crm.Application.Contracts project and should always use the application service interfaces to perform the user interactions.
The Acme.Crm.Web project still depends on the Acme.Crm.Application and Acme.Crm.EntityFrameworkCore projects since we need them at runtime. I have drawn these dependencies with dashed lines to indicate that these project dependencies should not exist in an ideal design, but are necessary for now. I will explain how we can get rid of those dependencies in the Separating the hosting from the UI section.
Separating the application contracts from the implementation brings a small problem that we will solve in the next section.
Once we have separated the contracts, we can no longer use the objects of the domain layer inside the contracts project because they have no reference to the domain layer, as shown in the previous section. This doesn't seem to be a problem at first glance. We shouldn't use these entities and other business objects in the application service contracts anyway – we should use DTOs instead. However, we still may want to reuse some types or values defined in the domain project.
For example, we may want to reuse a ProductType enum in a DTO class or depend on the same constant value for the product name's maximum length. We don't want to duplicate such code parts, but we also can't add a reference to the Acme.Crm.Domain project from the Acme.Crm.Application.Contracts project. The solution is to introduce a new project to declare such types and values.
We will name this new project Acme.Crm.Domain.Shared since this project will be part of the domain layer and shared with the rest of the solution. This project won't contain so many types in practice, but we still don't want to duplicate these types.
With the introduction of the Acme.Crm.Domain.Shared project, the new solution structure will be as follows:
The following diagram shows the dependencies between the projects in the solution:
The new Acme.Crm.Domain.Shared project is used by the Acme.Crm.Domain and Acme.Crm.Application.Contracts projects. In this way, directly or indirectly, all the other projects in the solution can use the types in that new project.
At this point, the fundamental layers of the ABP startup solution are complete. However, if you look at Figure 9.4, you will see that the ABP startup solution has three more projects. We will discuss these in the following subsections.
In Figure 9.4, you can see that the ABP startup solution has two HTTP-related projects.
First, the Acme.Crm.HttpApi project contains the API Controllers (that is, the REST APIs) of the solution. This project was introduced with the idea that separating the API from the UI would be better to organize and develop the solution.
Separating the HTTP API layer as a class library project makes some advanced scenarios possible by allowing them to be reused. The EventHub solution takes advantage of this separation by using the HTTP API layer as a proxy in the UI layer (the UI and HTTP API are hosted in different applications in that solution). See the Main website and Main HTTP API sections of Chapter 4, Understanding the Reference Solution, to learn how it works.
The second HTTP API-related project is Acme.Crm.HttpApi.Client. This is a class library project that is not being used for this example solution but can be used in more advanced scenarios. You can use this library from a client application (it can be your application or a third-party .NET client) to easily consume your HTTP APIs. It uses ABP's Dynamic C# Client Proxy system, as will be explained in Chapter 14, Building HTTP APIs and Real-Time Services. Most of the time, you don't make any changes to this project, but it automagically works. The EventHub solution uses this technique to perform HTTP API requests from the UI application.
By adding two new projects for the HTTP API layer, we now have eight projects in the solution, as shown in the following screenshot:
The following diagram shows the new dependency graph after adding these new projects (this time, I've removed the Acme.Crm. prefix from the project names to make them fit into the diagram):
The Acme.Crm.HttpApi and Acme.Crm.HttpApi.Client projects depend on the Acme.Crm.Application.Contracts project because the server and client share the same contracts (application service interfaces). The Acme.Crm.Web project depends on the Acme.Crm.HttpApi project since it serves the APIs at runtime. This example solution has a single application at runtime. You can revisit the EventHub solution structure that was provided in Chapter 4, Understanding the Reference Solution, to see these projects in a more complex environment with multiple applications at runtime.
Discarding the HTTP API Layer
Not every application needs to have HTTP APIs (that is, REST APIs). In this case, you can even remove this project from the solution. Also, if you like, you can move your API controllers to the Acme.Crm.Web project and discard the Acme.Crm.HttpApi project.
The next section will explain the last project in the solution.
In Figure 9.4, there is one more project named Acme.Crm.DbMigrator. This is a console application that can be used to apply EF Core code-first migrations to the database. It is a utility application and not part of the essential solution, so there is no need to investigate its details here.
Test Projects in the Solution
Besides these nine projects, there are six more projects in the solution under the test folder. They are unit/integration tests projects separately configured for each layer. One of them (Acme.Crm.HttpApi.Client.ConsoleTestApp) demonstrates how to consume HTTP APIs using the Acme.Crm.HttpApi.Client project. You can explore them yourself.
These are all the projects in the ABP startup solution. The solution structure that's been provided is the architectural model, followed by all the pre-built official ABP application modules. This model makes it possible to reuse the application modules in various scenarios, thanks to its flexibility and modularity.
In the next section, we will discuss an additional project that can be used to separate the hosting from the UI application.
One annoying thing in the architectural model shown in Figure 9.11 is that the Web project references the Application and EntityFramework projects. None of the pages/classes in the Web project directly use classes in these projects. However, since the Web project is the project that runs the application, we needed to reference these projects to make them available at runtime.
This structure is not a big problem, so long as you do not accidentally leak your domain and database layer objects into the presentation (web) layer. However, if you are worried and do not want to set development time dependencies for these runtime dependencies, you can add one more project, Acme.Crm.Web.Host, as shown in the following screenshot:
With this change, the Acme.Crm.Web project becomes a class library project rather than a final application. It only contains the presentation layer pages/components of the application; it does not contain the Startup.cs, Program.cs, and appsettings.json files. The Acme.Crm.Web.Host project becomes responsible for hosting by bringing all the projects together at runtime. It doesn't contain any application UI page or component.
I think this design is better. It gracefully extracts the hosting configuration details from the UI layer, removes the runtime dependencies, and keeps it more focused. However, we haven't separated the hosting application in the ABP startup template since most of the developers already find the ABP startup template complicated (compared to single-project ASP.NET Core startup templates). This is because there are many projects inside it, and we didn't want to add one more. I believe that a solution with multiple projects, and with less code in each project, is a better approach than a single project with everything in one place.
You can find the solution with a separate host project in this book's GitHub repository at https://github.com/PacktPublishing/Mastering-ABP-Framework/tree/main/Samples/Chapter-09/SeparateHosting and explore the structure provided.
In this section, you understood the roles that each project has in the ABP startup template, so you should be more comfortable while developing your solutions. In the next section, we will briefly revisit the EventHub reference solution from a DDD perspective.
So, we've learned the purpose of each of the projects in the ABP startup solution. It is a good starting point for a well-architected software solution. It sets up the layers properly, with a single domain layer and a single application layer (which is used by a single web application). However, in the real world, software solutions may be more complex. You may have multiple applications (on the same system) or may need to separate your domain into multiple sub-domains to reduce the complexity of each sub-domain.
DDD addresses the design of complex software solutions. One of the main purposes of separating the business logic into domain logic and application logic is to correctly organize your code base when there are multiple applications in your solution. When you have multiple applications, you have multiple application layers. Each of these layers implements the application-specific business logic of the related application, yet still shares the same core domain logic by using the same domain layer.
The EventHub project (introduced in Chapter 4, Understanding the Reference Solution) has two web applications. One of these applications is the main website that is used by end users. The other one is the admin (back office) application, which is used by system administrators. These applications have different user interfaces, different use cases, different authorization rules, and different performance, localization, caching, and scaling requirements. Separating these differences into two application layers helps us isolate these application-specific business and infrastructure requirements from each other. These applications share the core business logic that we don't want to duplicate across the applications. This means that two application layers use the same domain layer, as shown in the following diagram:
When we have multiple applications, separating the business logic between the application and domain layers becomes even more important. Leaking domain logic into the application layers duplicates it. On the other hand, placing application-specific logic in the domain layer leads you to coupling the business logic of different applications and writing many conditional statements to make the domain layer usable by these applications. Both of these situations make your code base buggy and difficult to maintain.
Domain logic versus application logic separation is important. We will return to this topic in Chapter 11, DDD – The Application Layer, after understanding the domain layer and the application layer building blocks. But before that, let's continue with the big picture and learn how a web request is executed in a DDD-based application.
We've introduced many building blocks and their descriptions, as well as how these building blocks are placed in layers in a .NET solution. In this section, we will explore how an HTTP request is executed in a typical web application that has been layered based on DDD. The following diagram shows the layers in action:
A request starts with a request from a client application. The client can be a browser that expects an HTML page (with its CSS/JavaScript files) or a data result (such as JSON). In this case, a Razor Page can process the request and returns an HTML page. If the application making the request is another kind of client (such as a console application), you probably respond to the request from an HTTP API (an API controller) endpoint and return a plain data result.
The MVC page (in the presentation layer) processes the UI logic, may perform some data conversions, and delegates the actual operation to a method of an application in the application layer. The application service may take a DTO, implement the use case logic, and return a resulting DTO to the presentation layer.
The application service internally uses the domain objects (entities, repositories, domain services, and more) to coordinate the business operation. The business operation should be a unit of work. This means it should be atomic. All the database operations in a use case (typically, an application method) should be committed or rolled back together.
The presentation and application layers typically implement the cross-cutting concerns, such as authorization, validation, exception handling, caching, audit logging, and so on.
As you learned in the previous chapters, ABP Framework provides a complete infrastructure for all these cross-cutting concerns and automates them wherever possible. It also provides proper base classes and practical conventions to help you structure your business components and implement DDD with best practices.
As the last part of this chapter, we will see some common principles of DDD in the next section.
DDD focuses on how you design your business code. It cares about state changes and how the business objects interact – how to create an entity, how to change its properties by applying (and even forcing) the business rules and constraints, and how to preserve the data validity and integrity.
DDD doesn't care about reporting or mass querying. You may take the power of a reporting tool to create cool dashboards for your application. You can fully use your underlying database provider's features for high performance. You can even duplicate the data in another database provider for read-only reporting purposes. You are free to do anything, so long as you don't mix the infrastructure details with your business code. All these are the concerns we should care about as a developer, but DDD doesn't care.
DDD also doesn't care about the infrastructure details; you are expected to isolate your business code from these details with proper abstractions. Two of these abstractions are especially important since they take a big place in your code base: the presentation technology and the database provider. In the next few sections, I will explain these two principles and discuss if we need to implement them.
It is a good practice to abstract the database integration in a DDD-based software solution. Your domain and application layers should be database and even ORM independent, in theory. There are some good reasons behind this suggestion. If you implement it, the following will occur:
The ABP startup template follows this principle – it doesn't include references to the database provider from the domain and application layers. ABP Framework already provides the infrastructure to implement the repository pattern easily. The ABP startup template also comes with the database layer, which uses an in-memory database instance for automated tests.
The last two of these reasons are important and easy to apply with ABP Framework. However, the first one is not so easy. In the beginning, it seems like you make your business code ORM/database independent when you place your data access logic behind the repositories. However, it is not that simple. Let's assume that you are currently using EF Core with SQL Server (a relational database) and want to design your business code and entities so that you can easily switch to MongoDB (a document database) later. If you want to accomplish that, you must take the following into account:
As you can see, being database-agnostic requires care when it comes to designing the entity and affects your code base.
You may be wondering, do you need it? Will you change the database provider in the future? If you change it later, how much effort do you need to make regarding that change? Is it more than your current effort to make it database-independent? Even if you try to do it, will it be truly database-independent (you may not know it before trying to switch)?
All ABP pre-built application modules are designed to be independent of the database provider, and the same business code works both on EF Core and MongoDB. This is necessary since they are reusable modules and can't assume a database provider. On the other hand, a final application can make this assumption. I still suggest hiding the data access code behind the repositories, and ABP makes this very easy. However, if you want to go with an EF Core dependency, I see no problem with that.
UI frameworks are the most dynamic systems in the software industry. There are plenty of alternatives, and the trending approaches and tools are rapidly changing. Coupling your business code with your UI code would be a bad idea.
Implementing this principle is more important and relatively easier, especially with ABP Framework. The ABP startup template comes with proper layering. ABP Framework provides many abstractions that you can use in your application and domain layers without depending on ASP.NET Core or any other UI framework.
In this first chapter on DDD, we looked at the four fundamental layers and the core building blocks in these layers. The ABP startup template is more complex than that four-layered structure. You learned how the startup template has evolved by one change at a time, and you understood the reasons behind these changes.
Regarding DDD, you learned that the business logic is separated into two layers: the application layer and the domain layer. We discussed how to deal with multiple applications that share the same domain logic by referencing the EventHub example solution.
We then understood how an HTTP request is executed and passed through the layers in a typical DDD-based software. Finally, we discussed isolating your application and domain layers from the infrastructure details, especially the database providers and UI frameworks.
This chapter aimed to show the big picture and the fundamental concepts of DDD. The next chapter will focus on implementing domain layer building blocks, such as aggregates, repositories, and domain services.
18.220.140.5