Modular Architecture

Domain Driven Design with .NET

Domain Driven Design (DDD) is a methodology and set of principles for building software systems that reflect and evolve with complex business domains. It helps teams tackle complexity by aligning the structure and language of the code with the core business.

This page explores the DDD approach and provides insight into how the ABP Framework supports its implementation in enterprise-grade .NET applications.

What is Domain-Driven Design?

In software development, dealing with complexity is one of the biggest challenges. As applications grow and the business domain becomes more intricate, maintaining a clear, flexible, and robust codebase becomes increasingly difficult. Domain-Driven Design (DDD) is a powerful approach specifically crafted to tackle this complexity by deeply connecting the software implementation to an evolving model of the core business domain.

DDD is best suited for complex domains and large-scale applications. For simple, short-lived CRUD (Create, Read, Update, Delete) applications, strictly following all DDD principles might be overkill. However, for systems where the core logic is complex and evolves over time, applying DDD principles and patterns can lead to a significantly more flexible, modular, and maintainable code base.

A strong understanding of Object-Oriented Programming (OOP) and SOLID principles is highly beneficial when implementing DDD, as DDD often implements and extends these concepts.

Fundamental Layers of Domain-Driven Design

Layering is a common technique to manage complexity and enhance reusability in software. DDD proposes a four-layered model that helps organize the business logic and separate it from infrastructure details.

Here are the four fundamental layers:

Domain Layer

This is the core of the application. It contains the essential business objects and implements the fundamental, use case-independent, and reusable business logic of the solution. This layer should ideally not depend on any other layer, while all other layers directly or indirectly depend on it.

Application Layer

This layer implements the specific use cases of the application. A use case typically corresponds to an action a user might take through the user interface. The application layer orchestrates and uses objects and logic from the domain layer to perform these use cases.

Presentation Layer

Also known as the UI layer, this layer contains all the user interface components, such as pages, views, JavaScript, and CSS files for a web application. It interacts with the application layer to initiate use cases and display information, but it should not directly interact with the domain layer or data access logic.

Infrastructure Layer

This layer supports the other layers by implementing the abstractions defined by them and integrating with third-party libraries and external systems like databases, caching, or messaging services. It's where the details of specific technologies reside, isolated from the core business logic.

This layering model is also often represented as Clean Architecture or Onion Architecture, where dependencies point inwards, with the Domain Layer at the core, being the most independent:

Core Building Blocks of Domain-Driven Design

From a technical standpoint, DDD heavily focuses on designing the business code within the Domain and Application layers.

Domain Layer Building Blocks

These components reside in the Domain Layer and implement the core business logic:

Entity

An object defined by its identity, which remains unique throughout its lifecycle. Entities have properties (state/data) and methods that encapsulate business logic operating on that state.

Value Object

An object defined by its attributes rather than an identity. Two value objects with the same attribute values are considered equal. They are generally simpler than entities and often designed as immutable.

Aggregate and Aggregate Root

An aggregate is a cluster of related objects (entities and value objects) treated as a single unit for data changes. The Aggregate Root is a specific entity within the aggregate that is the root of the tree and is responsible for maintaining the aggregate's integrity and consistency by enforcing business rules. All external access to the objects within an aggregate must go through the aggregate root.

Repository

An interface defined in the Domain Layer that acts as a collection-like abstraction for accessing domain objects (primarily aggregates) from the persistence layer. It hides the complexity of database queries and data mapping from the business logic.

Domain Service

A stateless class in the Domain Layer that encapsulates core business logic or rules that involve multiple aggregates or require external services.

Specification

A named, reusable, and combinable predicate or rule that can be used to filter or select domain objects based on specific business criteria.

Domain Event

An object that represents something significant that happened in the domain. They are used to signal other parts of the system that a change occurred in a loosely coupled manner.

Application Layer Building Blocks

These components reside in the Application Layer and define the application's use cases:

Application Service

A stateless class that implements specific application use cases. Application services consume and orchestrate domain objects (entities, repositories, and domain services) to achieve a task requested by the presentation layer. They typically accept and return Data Transfer Objects (DTOs).

Data Transfer Object (DTO)

A simple object without any business logic, used solely to carry data between the Application and Presentation layers.

Unit of Work (UOW)

Represents an atomic transaction boundary. All changes made within a unit of work should either all succeed and be committed together or all fail and be rolled back.

Execution Flow in a DDD-Based Application

Understanding how a request flows through the layers is key to grasping the interaction between the building blocks. Here is a diagram that shows interactions between the application components when a DDD-based web application gets an HTTP request:

A typical flow often starts with a user interaction in the Presentation Layer, triggering an HTTP request to the server.

  • 1 The Presentation Layer (e.g., an MVC Controller or Razor Page handler) receives the request. It handles UI-specific logic, potentially performs client-side or server-side validation for input format, and delegates the core operation to an Application Service method. The Presentation Layer uses DTOs to communicate with the Application Layer.
  • 2 The Application Layer's service method receives the DTO, performs application-specific validation and authorization checks. It implements the use case by coordinating objects from the Domain Layer (entities, domain services, repositories) to execute the necessary business logic. An application service method is considered a Unit of Work.
  • 3 The Domain Layer contains the entities with their encapsulated business logic and domain services for cross-aggregate logic. Repositories are used by the domain and application layers to interact with the persistence system.
  • 4 The Infrastructure Layer implements the details of interacting with the database or other external systems, providing the underlying implementation for repositories and potentially handling concerns like logging, caching, etc..
  • 5 Cross-Cutting Concerns like authorization, validation, exception handling, logging, caching, and transaction management are applied across multiple layers, often automatically or conventionally by frameworks.

Common Principles

DDD emphasizes designing the core business code while abstracting away infrastructure details:

Database Provider Independence

Ideally, the Domain and Application layers should not be tied to a specific database technology or Object-Relational Mapper (ORM). This is achieved by depending only on repository interfaces defined in the Domain Layer, which are implemented in the Infrastructure Layer. Benefits include potential future flexibility, better focus on business code, and easier testing.

Presentation Technology Agnostic

The Domain and Application layers must be completely independent of the UI framework or presentation technology used. UI technologies change frequently, and coupling core business logic to the UI would make it hard to maintain or switch technologies. Duplicate logic (like validation) may be necessary in both UI and application/domain layers, which is acceptable.

Focus on State Changes

DDD primarily focuses on how the state of domain objects changes and how business rules are enforced during these changes (create, update, delete operations). It does not typically concern itself with reporting or mass querying scenarios, which might require different architectural approaches, optimized queries, or even separate data sources.

Implementing Domain-Driven Design with ABP Framework

ABP Framework is designed with Domain-Driven Design at its core. It provides a robust infrastructure and opinionated architecture to help developers implement DDD patterns and best practices on top of .NET and ASP.NET Core.

The Startup Templates

ABP's startup templates are layered based on DDD principles, offering a clear structure to organize your code. While a simple DDD solution might have four projects (Domain, Application, Infrastructure, Presentation), ABP's startup template evolves this structure into a more detailed, practical organization suitable for larger applications and modular development.

The Building Blocks

ABP Framework provides significant infrastructure and conventions to simplify the implementation of DDD building blocks:

Entities and Aggregate Roots: DDD primarily focuses on how the state of domain objects changes and how business rules are enforced during these changes (create, update, delete operations). It does not typically concern itself with reporting or mass querying scenarios, which might require different architectural approaches, optimized queries, or even separate data sources.

Repositories: ABP provides a built-in, database-agnostic repository system. Generic repositories (IRepository) offer standard CRUD methods out of the box, typically for aggregate roots. Custom repositories can be implemented for advanced queries or provider-specific operations. ABP's implementation encourages placing repository interfaces in the Domain Layer and implementations in the Infrastructure Layer. A key principle, supported by ABP, is not to implement business logic within repositories.

Specifications: ABP provides a base Specification class and extension methods (And, Or, Not) to define and combine reusable, testable filters based on business rules. This helps encapsulate querying logic that would otherwise be duplicated in application services or repositories.

Domain Services: ABP provides the DomainService base class. Domain services are used for domain logic that spans multiple aggregates or requires dependencies. They work with domain objects and primitive types, but not DTOs.

Application Services: ABP provides a base ApplicationService class. Application services implement use cases, coordinating domain objects. They work with DTOs and should not expose domain entities to the presentation layer. ABP automatically handles the Unit of Work scope for application service methods, ensuring atomicity for each use case.

Data Transfer Objects: ABP provides a built-in, database-agnostic repository system. Generic repositories (IRepository) offer standard CRUD methods out of the box, typically for aggregate roots. Custom repositories can be implemented for advanced queries or provider-specific operations. ABP's implementation encourages placing repository interfaces in the Domain Layer and implementations in the Infrastructure Layer. A key principle, supported by ABP, is not to implement business logic within repositories.

Unit of Work (UOW): As mentioned, ABP's UOW system automates transaction management for application service methods and repository operations, ensuring data consistency.

Domain Events: ABP offers local and distributed event buses for publishing and handling domain events, enabling loosely coupled communication between aggregates or services.

Dealing with Multiple Applications

DDD is particularly valuable when dealing with systems composed of multiple applications sharing a common domain. ABP's layered architecture and module system facilitate this by allowing multiple application layers, each tailored to the specific needs of a different application (e.g., public website, admin panel, mobile app), while reusing a single domain layer.

Other Relevant ABP Features Supporting Domain-Driven Design

Beyond the core building blocks, ABP Framework provides extensive infrastructure for common cross-cutting concerns, further simplifying DDD implementation:

Validation: ABP integrates with ASP.NET Core's validation system and supports data annotations ([Required], [StringLength], etc.), IValidatableObject, and FluentValidation, automatically validating input DTOs in application services.

Authorization: ABP extends ASP.NET Core's authorization system with a permission system, allowing fine-grained control over access to application functionalities, typically enforced in the application layer.

Exception Handling: ABP provides a standardized exception handling infrastructure that automatically manages exceptions on the server side and client side, including user-friendly exceptions and mapping exceptions to HTTP status codes.

Data Filtering: ABP includes an infrastructure for applying global query filters, used internally for concerns like soft-delete and multi-tenancy, and extensible for custom filtering needs.

Audit Logging: ABP automatically logs changes to audited entities and method calls, providing traceability for operations.

Localization: ABP provides infrastructure for localizing the UI and application texts, crucial for multi-language support.

By providing a well-structured architecture and pre-built, multi-tenancy compatible infrastructure for these common concerns and DDD building blocks, ABP Framework allows developers to focus on implementing the unique business logic that adds value to the application, rather than reinventing the wheel for standard requirements.

Free E-Book: Implementing Domain Driven Design

Download the free e-book about implementing domain-driven design with .NET and ABP Framework.

Conclusion

Domain-Driven Design is a powerful approach for building complex, maintainable, and scalable software systems by focusing on the core domain logic. The ABP Framework provides a comprehensive infrastructure that aligns with DDD principles, offering pre-built tools, a structured solution template, and features like repositories, unit of work, and automatic API controllers. By leveraging ABP, developers can implement DDD efficiently, ensuring a clean, modular, and testable codebase that aligns with business needs.

To explore ABP’s capabilities, visit the ABP Framework documentation or directly get started with the ABP Framework for your DDD-based project.

Recommended Resources

For further reading, we recommend the following books: