Chapter 9: Understanding Domain-Driven Design

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:

  • Introducing DDD
  • Structuring a .NET solution based on DDD
  • Dealing with multiple applications
  • Understanding the execution flow
  • The common principles of DDD

Technical requirements

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.

Introducing DDD

Before we cover the implementation details, let's define DDD's core concepts and building blocks. Let's begin with the definition of DDD.

What is domain-driven design?

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.

DDD layers

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:

Figure 9.1 – Layers of DDD

Figure 9.1 – Layers of DDD

The preceding diagram shows the layers and their relationships:

  • The domain layer contains the essential business objects and implements the core, use case-independent, reusable domain logic of the solution. This layer doesn't depend on any other layer, but all the other layers directly or indirectly depend on it.
  • The application layer implements the use cases of the applications. A use case is typically an action that's taken by the user through the UI. The application layer uses the domain layer's objects to perform these use cases.
  • The presentation layer contains the UI components of the application, such as the views, JavaScript, and CSS files for a web application. It does not directly use the domain layer or database objects. Instead, it uses the application layer. Typically, for every use case/action that's performed on the UI, there is a corresponding functionality/method on the application layer.
  • The infrastructure layer depends on all the other layers and implements the abstractions that have been defined by these layers. It helps gracefully separate your business logic from third-party libraries and systems, such as database or cache providers.

Each layer of this model has a responsibility and contains various building blocks, which are introduced in the next section.

Building blocks

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:

  • Entity: An entity is a business object with a state (data) and business logic, both of which work with the properties of this entity. An entity has a unique identifier (ID) that is used to distinguish that entity from the others. This means that two entities with different identifiers are considered different entities, even if all the other properties are identical. For example, the EventHub solution contains the Event and Organization entities.
  • Value Object: A value object is another type of business object. Value objects are identified by their state (properties), and they don't have an identifier. This means that two value objects are considered the same if all their properties are the same. Value objects are generally simpler than entities and are typically implemented as immutable. For example, we can create value objects such as address, money, or date.
  • Aggregate and Aggregate Root: An aggregate is a cluster of objects (entities and value objects) that are bound together by an aggregate root object. The aggregate root is responsible for keeping the aggregate objects valid. It implements and coordinates the business logic on these objects. For example, the Event entity of the EventHub solution is the aggregate root entity of the Event aggregate, which contains tracks and sessions as sub-collections.
  • Repository: A repository is a collection-like interface that's used by the domain and application layers to access the persistence system. It hides the complexity of the database provider from the business code.
  • Domain Service: A domain service is a stateless service (class) that implements core business rules. It is useful to implement domain logic that depends on multiple aggregate types (so that none of these aggregates can be responsible for implementing that logic) or external services. Domain services get/return domain objects and are generally consumed by the application services or other domain services.
  • Specification: A specification is a named, reusable, testable, and combinable filter that's applied to business objects to select them based on specific business rules.
  • Domain Event: A domain event is a way of informing other services in a loosely coupled manner when a domain-specific event occurs. It is useful for implementing side effects across multiple aggregates.

The application layer implements the use case of the application by using the following building blocks:

  • Application Service: An application service is a stateless service (class) that implements the use cases of the application. It typically gets and returns data transfer objects, and its methods are used by the presentation layer. It uses and orchestrates the domain layer objects to perform a specific use case. A use case is typically implemented as a transactional (atomic) process.
  • Data Transfer Objects (DTO): A DTO is used to transfer data (state) between the presentation and application layers. It doesn't contain any business logic.
  • Unit of Work (UOW): A UOW is a transaction boundary. All the state changes (typically database operations) in a UOW must be implemented as atomic, committed together on success, or rolled back together on failure.

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.

Structuring a .NET solution based on DDD

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.

Creating a simple DDD-based .NET solution

Let's start from scratch and keep things simple by creating four projects in our .NET solution, as shown in the following screenshot:

Figure 9.2 – A simple DDD-based .NET solution in Visual Studio

Figure 9.2 – A simple DDD-based .NET solution in Visual Studio

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:

Figure 9.3 – Project dependencies of the simple DDD-based .NET solution


Figure 9.3 – Project dependencies of the simple DDD-based .NET solution

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:

  • Acme.Crm.Domain is a C# class library project that contains a Product class (aggregate root entity) and an IProductRepository interface (repository abstraction). The Product class represents a product and has some properties such as Id, Name, and Price. IProductRepository has some methods to perform database operations for products, such as Insert, Delete, and GetList.
  • Acme.Crm.Infrastructure is a C# class library project that contains the CrmDbContext class (the EF Core data context), which maps the Product entity to a database table. It also contains the EfProductRepository class, which implements the IproductRepository interface.
  • Acme.Crm.Application is a C# class library project that contains ProductAppService (application service), along with some methods to create, update, delete, and get a list of products. This service internally uses the IProductRepository interface and the Product entity (the domain objects).
  • Acme.Crm.Web is an ASP.NET Core MVC (Razor Pages) web application. It has a Products.cshtml page (and a related JavaScript file) that renders the product data on the UI and allows you to manage (create, edit, and delete) the products. It internally uses ProductAppService to perform the actual operations.

Now that we understand the purpose and contents of these projects, let's see why the projects have these dependencies:

  • Acme.Crm.Domain has no dependencies. In general, the domain layer has a minimal dependency and is abstracted from the infrastructural details.
  • Acme.Crm.Infrastructure has a dependency on the Acme.Crm.Domain project because it needs to access the Product class to map it to a database table, and it implements the IProductRepository interface.
  • Acme.Crm.Application has a dependency on the Acme.Crm.Domain project because it uses the IProductRepository repository and the Product entity to perform the use cases.
  • Finally, Acme.Crm.Web depends on the Acme.Crm.Application project because it uses the application service (ProductAppService).

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.

Evolution of the ABP startup solution

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:

Figure 9.4 – The CRM solution created using the ABP startup template

Figure 9.4 – The CRM solution created using the ABP startup template

Let's explain how this solution evolved from the four-project solution explained in the previous section.

Introducing the EntityFrameworkCore project

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:

Figure 9.5 – Introducing the Entity Framework Core integration project

Figure 9.5 – Introducing the Entity Framework Core integration project

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.

Introducing the application contracts

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:

  • The Acme.Crm.Application.Contracts project, which contains the application service interfaces (such as IProductAppService) and the related DTOs (such as ProductCreationDto).
  • The Acme.Crm.Application project, which contains the implementations of the application services (such as ProductAppService).

Introducing contracts (interfaces) for the application services has two important advantages:

  • The UI layer (the Acme.Crm.Web project here) can depend on the service contracts without depending on the implementation, and therefore the domain layer.
  • You can share the Acme.Crm.Application.Contracts project with a client application to rely on the same service interfaces and reuse the same DTO classes without sharing your business layers.

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:

Figure 9.6 – Introducing the application contracts project

Figure 9.6 – Introducing the application contracts project

With this new design, the project dependency graph will be like in the following figure:

Figure 9.7 – Project dependencies for the application contracts project

Figure 9.7 – Project dependencies for the application contracts project

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.

Introducing the domain shared project

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:

Figure 9.8 – Introducing the domain shared project

Figure 9.8 – Introducing the domain shared project

The following diagram shows the dependencies between the projects in the solution:

Figure 9.9 – Project dependencies for the domain shared project

Figure 9.9 – Project dependencies for the domain shared project

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.

Introducing the HTTP API layer

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:

Figure 9.10 – Adding the HTTP API projects to the solution

Figure 9.10 – Adding the HTTP API projects to the solution

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):

Figure 9.11 – Project dependencies for the HTTP API layer

Figure 9.11 – Project dependencies for the HTTP API layer

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.

Understanding the database migrator project

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.

Separating the hosting from the UI

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:

Figure 9.12 – Adding a separate hosting project

Figure 9.12 – Adding a separate hosting project

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.

Dealing with multiple applications

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:

Figure 9.13 – EventHub – multiple application layers and a single domain layer

Figure 9.13 – EventHub – multiple application layers and a single domain layer

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.

Understanding the execution flow

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:

Figure 9.14 – Execution flow through the layers

Figure 9.14 – Execution flow through the layers

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.

Understanding the common principles

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.

Database provider independence

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:

  • Your database provider (ORM or DBMS) may change in the future without affecting your business code. This makes your business code longer-lived.
  • Your domain and application layers become more focused on your business code by hiding the data access logic behind the repositories.
  • You can mock the database layer for automated tests more efficiently.

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:

  • You can't assume that you have the change tracking system of EF Core because the MongoDB .NET driver doesn't provide that feature. So, you should always manually update the changed entities at the end of your business logic.
  • You can't add navigation or collection properties to your entity where these properties are types of other aggregates. You must strictly implement the aggregate pattern (as will be explained in Chapter 10, DDD – The Domain Layer) by respecting the aggregate boundaries. This restriction deeply affects your entity design and the business code that works on your entities.

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.

Presentation technology-agnostic

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.

Summary

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.140.5