Merge branch 'rel-4.0' into dev

pull/6386/head
Halil İbrahim Kalkan 4 years ago
commit 2998301b2a

@ -12,6 +12,7 @@ The goals of this document are;
* Explain the **layered architecture** & solution structure offered by the ABP Framework.
* Introduce **explicit rules** to implement DDD patterns 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 software development **best practices** and our experiences to create a **maintainable codebase**.
### Simple Code!
@ -1103,6 +1104,8 @@ The only problem of this design is that `Issue.AssignedUserId` is now open to se
While there is a tradeoff between two approaches, we prefer to create Domain Services when the business logic requires to work with external services.
> If you don't have a good reason, we think **there is no need to create interfaces** (like `IIssueManager` for the `IssueManager`) for Domain Services.
### Application Services
An [Application Service](Application-Services.md) 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** (entities, repositories, etc.) to implement use cases.
@ -1179,4 +1182,797 @@ namespace IssueTracking.Issues
### Data Transfer Objects
TODO
A [DTO](Data-Transfer-Objects.md) is a simple object that is used to transfer state (data) between the Application and Presentation Layers. So, Application Service methods gets and returns DTOs.
#### Common DTO Principles & Best Practices
* A DTO **should be serializable**, by its nature. Because, most of the time it is transferred over network. So, it should have a **parameterless (empty) constructor**.
* Should not contain any **business logic**.
* **Never** inherit from or reference to **entities**.
**Input DTOs** (those are passed to the Application Service methods) have different natures than **Output DTOs** (those are returned from the Application Service methods). So, they will be treated differently.
#### Input DTO Best Practices
##### Do not Define Unused Properties for Input DTOs
Define **only the properties needed** for the use case! Otherwise, it will be **confusing for the clients** to use the Application Service method. You can surely define **optional properties**, but they should effect how the use case is working, when the client provides them.
This rule seems unnecessary first. Who would define unused parameters (input DTO properties) for a method? But it happens, especially when you try to reuse input DTOs.
##### Do not Re-Use Input DTOs
Define a **specialized input DTO for each use case** (Application Service method). Otherwise, some properties are not used in some cases and this violates the rule defined above: *Do not Define Unused Properties for Input DTOs*.
Sometimes, it seems appealing to reuse the same DTO class for two use cases, because they are almost same. Even if they are same now, they will probably become different by the time and you will come the same problem. **Code duplication is a better practice than coupling use cases**.
Another way of reusing input DTOs is **inheriting** DTOs from each other. While this can be useful in some rare cases, most of the time it brings you to the same point.
**Example: User Application Service**
````csharp
public interface IUserAppService : IApplicationService
{
Task CreateAsync(UserDto input);
Task UpdateAsync(UserDto input);
Task ChangePasswordAsync(UserDto input);
}
````
`IUserAppService` uses `UserDto` as the input DTO in all methods (use cases). `UserDto` is defined below:
````csharp
public class UserDto
{
public Guid Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public DateTime CreationTime { get; set; }
}
````
For this example;
* `Id` is not used in *Create* since the server determines it.
* `Password` is not used in *Update* since we have another method for it.
* `CreationTime` is never used since we can't allow client to send the Creation Time. It should be set in the server.
A true implementation can be like that:
````csharp
public interface IUserAppService : IApplicationService
{
Task CreateAsync(UserCreationDto input);
Task UpdateAsync(UserUpdateDto input);
Task ChangePasswordAsync(UserChangePasswordDto input);
}
````
With the given input DTO classes:
````csharp
public class UserCreationDto
{
public string UserName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
}
public class UserUpdateDto
{
public Guid Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
}
public class UserChangePasswordDto
{
public Guid Id { get; set; }
public string Password { get; set; }
}
````
This is more maintainable approach, while it seems you write more code.
**Exceptional Case**: There can be some exceptions for this rule: If you always want to develop two methods **in parallel**, they may share the same input DTO (by inheritance or direct reuse). For example, if you have a reporting page that has some filters and you have multiple Application Service methods (like screen report, excel report and csv report methods) use the same filters but returns different results, you may want to reuse the same filter input DTO to **couple these use cases**. Because, in this example, whenever you change a filter, you have to make the necessary changes in all the methods to have a consistent reporting system.
##### Input DTO Validation Logic
* Implement only **formal validation** inside the DTO. Use Data Annotation Validation Attributes or implement `IValidatableObject` for formal validation.
* **Do not perform domain validation**. For example, don't try to check unique username constraint in the DTOs.
**Example: Using Data Annotation Attributes**
````csharp
using System.ComponentModel.DataAnnotations;
namespace IssueTracking.Users
{
public class UserCreationDto
{
[Required]
[StringLength(UserConsts.MaxUserNameLength)]
public string UserName { get; set; }
[Required]
[EmailAddress]
[StringLength(UserConsts.MaxEmailLength)]
public string Email { get; set; }
[Required]
[StringLength(UserConsts.MaxEmailLength,
MinimumLength = UserConsts.MinPasswordLength)]
public string Password { get; set; }
}
}
````
ABP Framework automatically validates input DTOs, throws `AbpValidationException` and returns HTTP Status `400` to the client in case of an invalid input.
> Some developers think it is better to separate the validation rules and DTO classes. We think the declarative (Data Annotation) approach is practical and useful and doesn't cause any design problem. However, ABP also supports [FluentValidation integration](FluentValidation.md) if you prefer the other approach.
> See the [Validation document](Validation.md) for all validation options.
#### Output DTO Best Practices
* Keep output **DTO count minimum**. **Reuse** where possible (exception: Do not reuse input DTOs as output DTOs).
* Output DTOs can contain **more properties** than used in the client code.
* Return entity DTO from **Create** and **Update** methods.
The main goals of these suggestions are;
* Make client code easy to develop and extend;
* Dealing with **similar, but not same** DTOs are problematic on the client side.
* It is common to **need to other properties** on the UI/client in the future. Returning all properties (by considering security and privileges) of an entity makes client code easy to improve without requiring to touch to the backend code.
* If you are opening your API to **3rd-party clients**, you can't know requirements of each client.
* Make the server side code easy to develop and extend;
* You have less class to **understand and maintain**.
* You can reuse the Entity->DTO **object mapping** code.
* Returning same types from different methods make it easy and clear to create **new methods**.
**Example: Returning Different DTO types from different methods**
````csharp
public interface IUserAppService : IApplicationService
{
UserDto Get(Guid id);
List<UserNameAndEmailDto> GetUserNameAndEmail(Guid id);
List<string> GetRoles(Guid id);
List<UserListDto> GetList();
UserCreateResultDto Create(UserCreationDto input);
UserUpdateResultDto Update(UserUpdateDto input);
}
````
*(We haven't used async methods to make the example cleaner, but you use async in your real code!)*
The example code above returns different DTO types for each method. As you can guess, there will be a lot of code duplications for querying data, mapping entities to DTOs.
The `IUserAppService` service above can be simplified:
````csharp
public interface IUserAppService : IApplicationService
{
UserDto Get(Guid id);
List<UserDto> GetList();
UserDto Create(UserCreationDto input);
UserDto Update(UserUpdateDto input);
}
````
With a single output DTO:
````csharp
public class UserDto
{
public Guid Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public DateTime CreationTime { get; set; }
public List<string> Roles { get; set; }
}
````
* Removed `GetUserNameAndEmail` and `GetRoles` since `Get` method already returns the necessary information.
* `GetList` now returns same with `Get`.
* `Create` and `Update` also returns the same `UserDto`.
Using the same DTO has a lot of advantages as explained before. For example, think a scenario where you show a **data grid** of Users on the UI. After updating a user, you can get the return value and **update it on the UI**. So, you don't need to call `GetList` again. This is why we suggest to return the entity DTO (`UserDto` here) as return value from the `Create` and `Update` operations.
##### Discussion
Some of the output DTO suggestions may not fit every scenario. These suggestions can be ignored for **performance** reasons, especially when **large data sets** returned or when you create services for your own UI and you have **too many concurrent requests**.
In these cases, you may want to create **specialized output DTOs with minimal information**. The suggestions above are especially for applications where **maintaining the codebase** is more important than **negligible performance lost**.
#### Object to Object Mapping
Automatic [object to object mapping](Object-To-Object-Mapping.md) is a useful approach to copy values from one object to another when two objects have same or similar properties.
DTO and Entity classes generally have same/similar properties and you typically need to create DTO objects from Entities. ABP's [object to object mapping system](Object-To-Object-Mapping.md) with [AutoMapper](http://automapper.org/) integration makes these operations much easier comparing to manual mapping.
* **Use** auto object mapping only for **Entity to output DTO** mappings.
* **Do not use** auto object mapping for **input DTO to Entity** mappings.
There are some reasons why you **should not use** input DTO to Entity auto mapping;
1. An Entity class typically has a **constructor** that takes parameters and ensures valid object creation. Auto object mapping operation generally requires an empty constructor.
2. Most of the entity properties will have **private setters** and you should use methods to change these properties in a controlled way.
3. You typically need to **carefully validate and process** the user/client input rather than blindly mapping to the entity properties.
While some of these problems can be solved through mapping configurations (For example, AutoMapper allows to define custom mapping rules), it makes your business code **implicit/hidden** and **tightly coupled** to the infrastructure. We think the business code should be explicit, clear and easy to understand.
See the *Entity Creation* section below for an example implementation of the suggestions made in this section.
## Example Use Cases
This section will demonstrate some example use cases and discuss alternative scenarios.
### Entity Creation
Creating an object from an Entity / Aggregate Root class is the first step of the lifecycle of that entity. The *Aggregate / Aggregate Root Rules & Best Practices* section suggests to **create a primary constructor** for the Entity class that guarantees to **create a valid entity**. So, whenever we need to create an instance of that entity, we should always **use that constructor**.
See the `Issue` Aggregate Root class below:
````csharp
public class Issue : AggregateRoot<Guid>
{
public Guid RepositoryId { get; private set; }
public string Title { get; private set; }
public string Text { get; set; }
public Guid? AssignedUserId { get; internal set; }
public Issue(
Guid id,
Guid repositoryId,
string title,
string text = null
) : base(id)
{
RepositoryId = repositoryId;
Title = Check.NotNullOrWhiteSpace(title, nameof(title));
Text = text; //Allow empty/null
}
private Issue() { /* Empty constructor is for ORMs */ }
public void SetTitle(string title)
{
Title = Check.NotNullOrWhiteSpace(title, nameof(title));
}
//...
}
````
* This class guarantees to create a valid entity by its constructor.
* If you need to change the `Title` later, you need to use the `SetTitle` method which continues to keep `Title` in a valid state.
* If you want to assign this issue to a user, you need to use `IssueManager` (it implements some business rules before the assignment - see the *Domain Services* section above to remember).
* The `Text` property has a public setter, because it also accepts null values and haven't any validation rule for this example. It is also optional in the constructor.
Let's see an Application Service method that is used to create an issue:
````csharp
public class IssueAppService : ApplicationService, IIssueAppService
{
private readonly IssueManager _issueManager;
private readonly IRepository<Issue, Guid> _issueRepository;
private readonly IRepository<AppUser, Guid> _userRepository;
public IssueAppService(
IssueManager issueManager,
IRepository<Issue, Guid> issueRepository,
IRepository<AppUser, Guid> userRepository)
{
_issueManager = issueManager;
_issueRepository = issueRepository;
_userRepository = userRepository;
}
public async Task<IssueDto> CreateAsync(IssueCreationDto input)
{
// Create a valid entity
var issue = new Issue(
GuidGenerator.Create(),
input.RepositoryId,
input.Title,
input.Text
);
// Apply additional domain actions
if (input.AssignedUserId.HasValue)
{
var user = await _userRepository.GetAsync(input.AssignedUserId.Value);
await _issueManager.AssignToAsync(issue, user);
}
// Save
await _issueRepository.InsertAsync(issue);
// Return a DTO represents the new Issue
return ObjectMapper.Map<Issue, IssueDto>(issue);
}
}
````
`CreateAsync` method;
* Uses the `Issue` **constructor** to create a valid issue. It passes the `Id` using the [IGuidGenerator](Guid-Generation.md) service. It doesn't use auto object mapping here.
* If client wants to **assign this issue to a user** on object creation, it uses the `IssueManager` to do it by allowing the `IssueManager` to perform the necessary checks before this assignment.
* **Saves** the entity to the database.
* Finally uses the `IObjectMapper` to return an `IssueDto` that is automatically created by **mapping** from the new `Issue` entity.
#### Applying Domain Rules on Entity Creation
The example `Issue` entity has no business rule on entity creation, except some formal validations in the constructor. However, there maybe scenarios where entity creation should check some extra business rules.
For example, assume that you **don't want** to allow to create an issue if there is already an issue with **exactly the same `Title`**. Where to implement this rule? It is **not proper** to implement this rule in the **Application Service**, because it is a **core business (domain) rule** that should always be checked.
This rule should be implemented in a **Domain Service**, `IssueManager` in this case. So, we need to force the Application Layer always to use the `IssueManager` to create a new `Issue.`
First, we can make the `Issue` constructor `internal`, instead of `public`:
````csharp
public class Issue : AggregateRoot<Guid>
{
//...
internal Issue(
Guid id,
Guid repositoryId,
string title,
string text = null
) : base(id)
{
RepositoryId = repositoryId;
Title = Check.NotNullOrWhiteSpace(title, nameof(title));
Text = text; //Allow empty/null
}
//...
}
````
This prevents Application Services to directly use the constructor, so they will use the `IssueManager`. Then we can add a `CreateAsync` method to the `IssueManager`:
````csharp
using System;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services;
namespace IssueTracking.Issues
{
public class IssueManager : DomainService
{
private readonly IRepository<Issue, Guid> _issueRepository;
public IssueManager(IRepository<Issue, Guid> issueRepository)
{
_issueRepository = issueRepository;
}
public async Task<Issue> CreateAsync(
Guid repositoryId,
string title,
string text = null)
{
if (await _issueRepository.AnyAsync(i => i.Title == title))
{
throw new BusinessException("IssueTracking:IssueWithSameTitleExists");
}
return new Issue(
GuidGenerator.Create(),
repositoryId,
title,
text
);
}
}
}
````
* `CreateAsync` method checks if there is already an issue with the same title and throws a business exception in this case.
* If there is no duplication, it create and returns a new `Issue`.
The `IssueAppService` is changed as shown below in order to use the `IssueManager`'s `CreateAsync` method:
````csharp
public class IssueAppService : ApplicationService, IIssueAppService
{
private readonly IssueManager _issueManager;
private readonly IRepository<Issue, Guid> _issueRepository;
private readonly IRepository<AppUser, Guid> _userRepository;
public IssueAppService(
IssueManager issueManager,
IRepository<Issue, Guid> issueRepository,
IRepository<AppUser, Guid> userRepository)
{
_issueManager = issueManager;
_issueRepository = issueRepository;
_userRepository = userRepository;
}
public async Task<IssueDto> CreateAsync(IssueCreationDto input)
{
// Create a valid entity using the IssueManager
var issue = await _issueManager.CreateAsync(
input.RepositoryId,
input.Title,
input.Text
);
// Apply additional domain actions
if (input.AssignedUserId.HasValue)
{
var user = await _userRepository.GetAsync(input.AssignedUserId.Value);
await _issueManager.AssignToAsync(issue, user);
}
// Save
await _issueRepository.InsertAsync(issue);
// Return a DTO represents the new Issue
return ObjectMapper.Map<Issue, IssueDto>(issue);
}
}
// *** IssueCreationDto class ***
public class IssueCreationDto
{
public Guid RepositoryId { get; set; }
[Required]
public string Title { get; set; }
public Guid? AssignedUserId { get; set; }
public string Text { get; set; }
}
````
##### Discussion: Why not saved Issue to database in the `IssueManager`?
You may ask "**Why `IssueManager` hasn't saved the `Issue` into the database?**". We think it is the responsibility of the Application Service.
Because, the Application Service may require additional changes/operations on the `Issue` object before saving it. If Domain Service saves it, then the *Save* operation is duplicated;
* It causes performance lost because of double database round trip.
* It requires explicit database transaction that covers both operations.
* If additional actions cancel the entity creation because of a business rule, the transaction should be rolled back in the database.
When you check the `IssueAppService`, you see the advantage of **not saving** `Issue` to database in the `IssueManager.CreateAsync`. Otherwise, we would need to perform one *Insert* (in the `IssueManager`) and one *Update* (after the Assignment).
##### Discussion: Why not implemented the duplicate Title check in the Application Service?
We could simple say "Because it is a **core domain logic** and should be implemented in the Domain Layer". However, it brings a new question "**How did you decide** that it is a core domain logic, but not an application logic?" (we will discuss the difference later with more details).
For this example, a simple question can help us to make the decision: "If we have another way (use case) of creating an issue, should we still apply the same rule? Is that rule should *always* be implemented". You may think "Why we have a second way of creating an issue?". However, in real life, you have;
* **End users** of the application may create issues in your application's standard UI.
* You may have a second **back office** application that is used by your own employee and you may want to provide a way of creating issues (probably with different authorization rules in this case).
* You may have an HTTP API that is open to **3rd-party clients** and they create issues.
* You may have a **background worker** service that do something and creates issues if it detects some problems. In this way, it will create an issue without any user interaction (and probably without any standard authorization check).
* You may have a button on the UI that **converts** something (for example, a discussion) to an issue.
We can give more examples. All of these are should be implemented by **different Application Service methods** (see the *Multiple Application Layers* section below), but they **always** follow the rule: Title of the new issue can not be same of any existing issue! That's why this logic is a **core domain logic**, should be located in the Domain Layer and **should not be duplicated** in all these application service methods.
### Updating / Manipulating An Entity
Once an entity is created, it is updated/manipulated by the use cases until it is deleted from the system. There can be different type of use cases directly or indirectly changes an entity.
In this section, we will discuss a typical update operation that changes multiple properties of an `Issue`.
This time, beginning from the *Update* DTO:
````csharp
public class UpdateIssueDto
{
[Required]
public string Title { get; set; }
public string Text { get; set; }
public Guid? AssignedUserId { get; set; }
}
````
By comparing to `IssueCreationDto`, you see no `RepositoryId`. Because, our system doesn't allow to move issues across repositories (think as GitHub repositories). Only `Title` is required and the other properties are optional.
Let's see the *Update* implementation in the `IssueAppService`:
````csharp
public class IssueAppService : ApplicationService, IIssueAppService
{
private readonly IssueManager _issueManager;
private readonly IRepository<Issue, Guid> _issueRepository;
private readonly IRepository<AppUser, Guid> _userRepository;
public IssueAppService(
IssueManager issueManager,
IRepository<Issue, Guid> issueRepository,
IRepository<AppUser, Guid> userRepository)
{
_issueManager = issueManager;
_issueRepository = issueRepository;
_userRepository = userRepository;
}
public async Task<IssueDto> UpdateAsync(Guid id, UpdateIssueDto input)
{
// Get entity from database
var issue = await _issueRepository.GetAsync(id);
// Change Title
await _issueManager.ChangeTitleAsync(issue, input.Title);
// Change Assigned User
if (input.AssignedUserId.HasValue)
{
var user = await _userRepository.GetAsync(input.AssignedUserId.Value);
await _issueManager.AssignToAsync(issue, user);
}
// Change Text (no business rule, all values accepted)
issue.Text = input.Text;
// Update entity in the database
await _issueRepository.UpdateAsync(issue);
// Return a DTO represents the new Issue
return ObjectMapper.Map<Issue, IssueDto>(issue);
}
}
````
* `UpdateAsync` method gets `id` as a separate parameter. It is not included in the `UpdateIssueDto`. This is a design decision that helps ABP to properly define HTTP routes when you [auto expose](API/Auto-API-Controllers.md) this service as an HTTP API endpoint. So, that's not related to DDD.
* It starts by **getting** the `Issue` entity **from database**.
* Uses `IssueManager`'s `ChangeTitleAsync` instead of directly calling `Issue.SetTitle(...)`. Because we need to implement the **duplicate Title check** as just done in the *Entity Creation*. This requires some changes in the `Issue` and `IssueManager` classes (will be explained below).
* Uses `IssueManager`'s `AssignToAsync` method if the **assigned user** is being changed with this request.
* Directly sets the `Issue.Text` since there is **no business rule** for that. If we need later, we can always refactor.
* **Saves changes** to database. Again, saving changed entities is a responsibility of the Application Service method that coordinates the business objects and the transaction. If `IssueManager` had saved internally in `ChangeTitleAsync` and `AssignToAsync` method, there would be double database operation (see the *Discussion: Why not saved Issue to database in the `IssueManager`?* above).
* Finally uses the `IObjectMapper` to return an `IssueDto` that is automatically created by **mapping** from the updated `Issue` entity.
As said, we need some changes in the `Issue` and `IssueManager` classes.
First, made `SetTitle` internal on the `Issue` class:
````csharp
internal void SetTitle(string title)
{
Title = Check.NotNullOrWhiteSpace(title, nameof(title));
}
````
Then added a new method on the `IssueManager` to change the Title:
````csharp
public async Task ChangeTitleAsync(Issue issue, string title)
{
if (issue.Title == title)
{
return;
}
if (await _issueRepository.AnyAsync(i => i.Title == title))
{
throw new BusinessException("IssueTracking:IssueWithSameTitleExists");
}
issue.SetTitle(title);
}
````
## Domain Logic & Application Logic
As mentioned before, *Business Logic* in the Domain Driven Design is splitted into two parts (layers): *Domain Logic* and *Application Logic*:
![domain-driven-design-domain-vs-application-logic](images/domain-driven-design-domain-vs-application-logic.png)
Domain Logic consists of the *Core Domain Rules* of the system while Application Logic implements application specific *Use Cases*.
While the definition is clear, the implementation may not be easy. You may be undecided which code should stand in the Application Layer, which code should be in the Domain Layer. This section tries to explain the differences.
### Multiple Application Layers
DDD helps to **deal with complexity** when your system is large. Especially, if there are **multiple applications** are being developed on a **single domain,** then the **Domain Logic vs Application Logic separation** becomes much more important.
Assume that you are building a system that has multiple applications;
* A **Public Web Site Application**, built with ASP.NET Core MVC, to show your products to users. Such a web site doesn't require authentication to see the products. The users login to the web site, only if they are performing some actions (like adding a product to the basket).
* A **Back Office Application**, built with Angular UI (that uses REST APIs). This application used by office workers of the company to manage the system (like editing product descriptions).
* A **Mobile Application** that has much simpler UI compared to the Public Web Site. It may communicate to the server via REST APIs or another technology (like TCP sockets).
![domain-driven-design-multiple-applications](images/domain-driven-design-multiple-applications.png)
Every application will have different **requirements**, different **use cases** (Application Service methods), different **DTOs**, different **validation** and **authorization** rules... etc.
Mixing all these logics into a single application layer makes your services contain too many `if`s with **complicated business logic** makes your code **hard to develop, maintain and test** and leads to potential bugs.
If you've multiple applications with a single domain;
* Create **separate application layers** for each application/client type and implement application specific business logic in these separate layers.
* Use a **single domain layer** to share the core domain logic.
Such a design makes it even more important to distinguish between Domain logic and Application Logic.
To be more clear about the implementation, you can create different projects (`.csproj`) for each application types. For example;
* `IssueTracker.Admin.Application` & `IssueTracker.Admin.Application.Contacts` projects for the Back Office (admin) Application.
* `IssueTracker.Public.Application` & `IssueTracker.Public.Application.Contracts` projects for the Public Web Application.
* `IssueTracker.Mobile.Application` & `IssueTracker.Mobile.Application.Contracts` projects for the Mobile Application.
### Examples
This section contains some Application Service and Domain Service examples to discuss how to decide to place business logic inside these services.
**Example: Creating a new `Organization` in a Domain Service**
````csharp
public class OrganizationManager : DomainService
{
private readonly IRepository<Organization> _organizationRepository;
private readonly ICurrentUser _currentUser;
private readonly IAuthorizationService _authorizationService;
private readonly IEmailSender _emailSender;
public OrganizationManager(
IRepository<Organization> organizationRepository,
ICurrentUser currentUser,
IAuthorizationService authorizationService,
IEmailSender emailSender)
{
_organizationRepository = organizationRepository;
_currentUser = currentUser;
_authorizationService = authorizationService;
_emailSender = emailSender;
}
public async Task<Organization> CreateAsync(string name)
{
if (await _organizationRepository.AnyAsync(x => x.Name == name))
{
throw new BusinessException("IssueTracking:DuplicateOrganizationName");
}
await _authorizationService.CheckAsync("OrganizationCreationPermission");
Logger.LogDebug($"Creating organization {name} by {_currentUser.UserName}");
var organization = new Organization();
await _emailSender.SendAsync(
"systemadmin@issuetracking.com",
"New Organization",
"A new organization created with name: " + name
);
return organization;
}
}
````
Let's see the `CreateAsync` method part by part to discuss if the code part should be in the Domain Service, or not;
* **CORRECT**: It first checks for **duplicate organization name** and and throws exception in this case. This is something related to core domain rule and we never allow duplicated names.
* **WRONG**: Domain Services should not perform **authorization**. [Authorization](Authorization.md) should be done in the Application Layer.
* **WRONG**: It logs a message with including the [Current User](CurrentUser.md)'s `UserName`. Domain service should not be depend on the Current User. Domain Services should be usable even if there is no user in the system. Current User (Session) should be a Presentation/Application Layer related concept.
* **WRONG**: It sends an [email](Emailing.md) about this new organization creation. We think this is also a use case specific business logic. You may want to create different type of emails in different use cases or don't need to send emails in some cases.
**Example: Creating a new `Organization` in an Application Service**
````csharp
public class OrganizationAppService : ApplicationService
{
private readonly OrganizationManager _organizationManager;
private readonly IPaymentService _paymentService;
private readonly IEmailSender _emailSender;
public OrganizationAppService(
OrganizationManager organizationManager,
IPaymentService paymentService,
IEmailSender emailSender)
{
_organizationManager = organizationManager;
_paymentService = paymentService;
_emailSender = emailSender;
}
[UnitOfWork]
[Authorize("OrganizationCreationPermission")]
public async Task<Organization> CreateAsync(CreateOrganizationDto input)
{
await _paymentService.ChargeAsync(
CurrentUser.Id,
GetOrganizationPrice()
);
var organization = await _organizationManager.CreateAsync(input.Name);
await _organizationManager.InsertAsync(organization);
await _emailSender.SendAsync(
"systemadmin@issuetracking.com",
"New Organization",
"A new organization created with name: " + input.Name
);
return organization; // !!!
}
private double GetOrganizationPrice()
{
return 42; //Gets from somewhere else...
}
}
````
Let's see the `CreateAsync` method part by part to discuss if the code part should be in the Application Service, or not;
* **CORRECT**: Application Service methods should be unit of work (transactional). ABP's [Unit Of Work](Unit-Of-Work.md) system makes this automatic (even without need to add `[UnitOfWork]` attribute for the Application Services).
* **CORRECT**: [Authorization](Authorization.md) should be done in the application layer. Here, it is done by using the `[Authorize]` attribute.
* **CORRECT**: Payment (an infrastructure service) is called to charge money for this operation (Creating an Organization is a paid thing in our business).
* **CORRECT**: Application Service method is responsible to save changes to the database.
* **CORRECT**: We can send [email](Emailing.md) as a notification to the system admin.
* **WRONG**: Do not return entities from the Application Services. Return a DTO instead.
**Discussion: Why not moving the payment logic inside the domain service?**
You may wonder why the payment code is not inside the `OrganizationManager`. It is an **important thing** and we never want to **miss the payment**.
However, **being important is not sufficient** to consider a code as a Core Business Logic. We may have **other use cases** where we don't charge money to create a new Organization. Examples;
* An admin user can use a Back Office Application to create a new organization without any payment.
* A background-working data import/integration/synchronization system may also need to create organizations without any payment operation.
As you see, **payment is not a necessary operation to create a valid organization**. It is a use case specific application logic.
**Example: CRUD Operations**
````csharp
public class IssueAppService
{
private readonly IssueManager _issueManager;
public IssueAppService(IssueManager issueManager)
{
_issueManager = issueManager;
}
public async Task<IssueDto> GetAsync(Guid id)
{
return await _issueManager.GetAsync(id);
}
public async Task CreateAsync(IssueCreationDto input)
{
await _issueManager.CreateAsync(input);
}
public async Task UpdateAsync(UpdateIssueDto input)
{
await _issueManager.UpdateAsync(input);
}
public async Task DeleteAsync(Guid id)
{
await _issueManager.DeleteAsync(id);
}
}
````
This Application Service **does nothing** itself and **delegates all the work** to the *Domain Service*. It even passes the DTOs to the `IssueManager`.
* **Do not** create Domain Service methods just for simple **CRUD** operations **without any domain logic**.
* **Never** pass **DTOs** to or return **DTOs** from the Domain Services.
Application Services can directly work with repositories to query, create, update or delete data unless there are some domain logics should be performed during these operations. In such cases, create Domain Service methods, but only for those really necessary.
> Do not create such CRUD domain service methods just by thinking that they may be needed in the future ([YAGNI](https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it))! Do it when you need and refactor the existing code. Since the Application Layer gracefully abstracts the Domain Layer, the refactoring process doesn't affect the UI Layer and other clients.
## Reference Books
If you seriously interest in the Domain Driven Design and building large scale enterprise systems, the following books are suggested as reference books;
* "*Domain Driven Design*" by Eric Evans
* "*Implementing Domain Driven Design*" by Vaughn Vernon
* "*Clean Architecture*" by Robert C. Martin

@ -10,7 +10,7 @@ ABP framework provides an **infrastructure** to make **Domain Driven Design** ba
> - Basing complex designs on a model of the domain;
> - Initiating a creative collaboration between technical and domain experts to iteratively refine a conceptual model that addresses particular domain problems.
### Layers
## Layers & Building Blocks
ABP follows DDD principles and patterns to achieve a layered application model which consists of four fundamental layers:
@ -19,11 +19,7 @@ ABP follows DDD principles and patterns to achieve a layered application model w
- **Domain Layer**: Includes business objects and the core (domain) business rules. This is the heart of the application.
- **Infrastructure Layer**: Provides generic technical capabilities that support higher layers mostly using 3rd-party libraries.
DDD mostly interest in the **Domain** and the **Application** layers, rather than the Infrastructure and the Presentation layers.
## Contents
See the following documents to learn what ABP Framework provides to you to implement DDD in your project.
DDD mostly interest in the **Domain** and the **Application** layers, rather than the Infrastructure and the Presentation layers. The following documents explains the **infrastructure** provided by the ABP Framework to implement **Building Blocks** of the DDD:
* **Domain Layer**
* [Entities & Aggregate Roots](Entities.md)
@ -35,3 +31,7 @@ See the following documents to learn what ABP Framework provides to you to imple
* [Application Services](Application-Services.md)
* [Data Transfer Objects (DTOs)](Data-Transfer-Objects.md)
* [Unit of Work](Unit-Of-Work.md)
## The Ultimate DDD Implementation Guide
See the [Implementing Domain Driven Design](Domain-Driven-Design-Implementation-Guide.md) guide as a **complete reference**. The Guide explains the Domain Driven Design and introduces explicit **rules and examples** to give a deep understanding of the **implementation details**.

@ -34,8 +34,12 @@ The following tools should be installed on your development machine:
<sup id="f-editor"><b>1</b></sup> _You can use another editor instead of Visual Studio as long as it supports .NET Core and ASP.NET Core._ <sup>[↩](#a-editor)</sup>
{{ if UI != "Blazor" }}
<sup id="f-yarn"><b>2</b></sup> _Yarn v2 works differently and is not supported._ <sup>[↩](#a-yarn)</sup>
{{ end }}
### Install the ABP CLI
[ABP CLI](./CLI.md) is a command line interface that is used to automate some common tasks for ABP based solutions.

@ -377,6 +377,10 @@
"path": "Unit-Of-Work.md"
}
]
},
{
"text": "Guide: Implementing DDD",
"path": "Domain-Driven-Design-Implementation-Guide.md"
}
]
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Loading…
Cancel
Save