Use case demonstration - Create entities
This section demonstrates some sample use cases and discusses alternative scenarios .
Create entities
From entity / Aggregating the root class to create objects is the first step in the entity life cycle . polymerization / Aggregate root rules and best practices section The suggestion is Entity class Create a primary constructor , In order to make sure Create a valid entity . therefore , Whenever we need to create an instance of an entity , We should all Use that constructor
See the following question aggregate root classes :
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; private 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 null value
}
private Issue() { // by ORM Reserved empty constructor }
public void SetTitle(string title)
{
Title = Check.NotNullOrWhiteSpace(title, nameof(title));
}
}
This class is guaranteed by its constructor Create a valid entity .
If you need to change the title , You need to use SetTitle Method Ensure that the title is in a valid state
If you want to assign this question to a user , You need to use IssueManager ( It implements some business rules before allocation , Please refer to my previous discussion about Field service The article ).
Text Property has a public setter, Because it also accepts null value , And this example doesn't have any validation rules . It is also optional in the constructor
Let's look at the Application Service Method :
public class IssueAppService : ApplicationService, IIssueAppService
{
// omitted Repository and DomainService Dependency injection of
[Authorize]
public async Task<IssueDto> CreateAsync(IssueCreationDto input)
{
// Create a valid problem entity
var issue = new Issue(
GuidGenerator.Create(),
input.RepositoryId,
input.Title,
input.Text
);
// If an assignee is passed in , Then assign the problem method to this user
if(input.AssignedUserId.HasValue)
{
var user = await _userRepository.GetAsync(input.AssignedUserId.Value);
await _issueManager.AssignToAsync(issue, user);
}
// Save the problem entity to the database
await _issueRepository.InsertAsync(issue);
// Returns the... That represents the new problem DTO
return ObjectMapper.Map<Issue, IssueDto>(issue);
}
}
CreateAsync Method :
- Use Issue Constructors create valid questions . It USES IGuidGenerator Service delivery Id. Automatic object mapping is not used here
- If the client wants to assign this problem to the user when the object is created , It will use IssueManager To complete , allow IssueManager Perform the necessary checks before allocation .
- Save entity to database
- Finally using IObjectMapper Return to one IssueDto , The IssueDto It is through mapping from New Issue Entities are automatically created
Create entities using domain rules
The above example , Issue There are no business rules for entity creation , In addition to doing some form of validation in the constructor . however , In some cases , Entity creation should check some additional business rules
for example , Suppose you don't want to Exactly the same title Create a problem when a problem already exists . Where to implement this rule ? stay Application Service It is not appropriate to implement this rule in , Because it is one that should always be checked The core business ( field ) The rules
The rule should be in Field service ( In this case, it's IssueManager ) To realize . therefore , We need to force the application layer to always use IssueManager To create a new Issue
First , We can Issue Constructor set to internal , instead of public:
public class Issue : AggregateRoot<Guid>
{
internal Issue(
Guid id,
Guid repositoryId,
string title,
string text = null
) : base(id)
{
//...
}
}
This prevents application services from using constructors directly , So they will use IssueManager . Then we can go to IssueManager Add a CreateAsync Method :
public class IssueManager : DomainService
{
// Omit dependency injection
public async Task<IssueDto> CreateAsync(
Guid repositoryId,
string title,
string text = null
)
{
// If there is a problem with the same title , Direct throw
if(await _issueRepository.AnyAsync(i => i.Title == title))
{
throw new BusinessException("IssueTracking:IssueWithSameTitleExists");
}
// Create a valid problem entity
return new Issue(
GuidGenerator.Create(),
repositoryId,
title,
text
);
}
}
CreateAsyncMethod to check whether the same title already has a problem , And throw a business exception in this case- If there is no repetition , Create and return a new Issue
In order to use the above method ,IssueAppService It is modified as follows :
public class IssueAppService : ApplicationService, IIssueAppService
{
// Omit dependency injection
public async Task<IssueDto> CreateAsync(IssueCreationDto input)
{
//* Modify to create a valid problem entity through domain services , Not directly new
var issue = await _issueManager.CreateAsync(
GuidGenerator.Create(),
input.RepositoryId,
input.Title,
input.Text
);
// If an assignee is passed in , Then assign the problem method to this user
if(input.AssignedUserId.HasValue)
{
var user = await _userRepository.GetAsync(input.AssignedUserId.Value);
await _issueManager.AssignToAsync(issue, user);
}
// Save the problem entity to the database
await _issueRepository.InsertAsync(issue);
// Returns the... That represents the new problem DTO
return ObjectMapper.Map<Issue, IssueDto>(issue);
}
}
Discuss : Why doesn't the problem lie in IssueManager Save to database ?
You may ask “ Why? IssueManager Do not save the problem to the database ?” We think it's Responsibility for application services
because , Before saving the problem object , Application services may need to make additional changes to them / operation . If the domain service saves it , The save operation will be repeated
- Two round trips to the database can result in performance loss
- An explicit database transaction is required to contain these two operations
- If due to business rules , Other operations cancel entity creation , The transaction should be rolled back in the database
When you check IssueAppService when , You will see it in IssueManager.CreateAsync Don't save Issue To the database . otherwise , We will need to perform an insert ( stay IssueManager in ) And an update ( After the assignment question )
Discuss : Why not implement duplicate Title Checking in application services ?
We can simply say “ Because it's a Core domain logic , It should be implemented in the domain layer ”. However , This brings a new problem : “ How do you determine that it is the core domain logic , Not application logic ?” ( We will discuss the differences in detail later )
For this example , A simple question can help us make a decision : “ If we had another way ( Use cases ) To create a problem , Whether we still apply the same rules ?” You might think “ Why do we have a second way to create problems ?” However , in real life , Do you have :
- The end user of the application may be in the standard UI Create a problem in ( For example github Web page creation problem )
- You may have a second Background applications , Used by your own employees , You might want to provide a way to create problems ( In this case, different authorization rules may be used )
- You may have a pair of Third party clients Open HTTP API, They create problems .
- You may have a background worker service, If it detects some faults , It does something and creates problems . such , It will work without any user interaction ( There may not be any standard authorization checks ) Create questions .
- You can even go to UI Set a button on the , Put some content ( for example , Discuss ) transformation For the problem
in summary , Different applications always follow these rules : The title of a new question cannot be the same as that of any existing question ! They have nothing to do with the application layer ! This is why this logic is the core domain logic , Should be in the domain layer , It should not be implemented as duplicate code in application services .







