Focusing on the two core technologies of DDD and ABP Framework, a series of articles on core component implementation and comprehensive case implementation will be released later. Please pay attention!
ABP Framework workshop (QQ group: 726299208)
ABP Framework learning and DDD implementation experience sharing; Sample source code, e-book sharing, welcome to join!
Domain services
Domain services implement domain logic, which:
- Rely on services and warehousing.
- Multiple aggregations are required to implement logic that cannot be handled by a single aggregation.
Domain services are used together with domain objects. Their methods can obtain and return entities, value objects, primitive types, etc. However, it does not get / return DTOs, which belong to the application layer.
Example: assigning questions to users
Recall how we used to assign problems to users
public class Issue:AggregateRoot<Guid> { //.. //User ID associated with the problem public Guid? AssignedUserId{get;private set;} //Allocation method public async Task AssignToAsync(AppUser user,IUserIssueService userIssueService) { var openIssueCount = await userIssueService.GetOpenIssueCountAsync(user.Id); if(openIssueCount >=3 ) { throw new BusinessException("IssueTracking:CanNotOpenLockedIssue"); } AssignedUserId=user.Id; } public void CleanAssignment() { AssignedUserId=null; } }
Now, we will migrate the logic to domain services. First, modify the Issue class:
public class Issue:AggregateRoot<Guid> { //... public Guid? AssignedUserId{get;internal set;} }
- Remove the AssignToAsync method from the aggregation (because it needs to be implemented in the corresponding domain service.)
- Change the AssignedUserId property setter from private to internal to allow it to be set from a domain service.
Next, create a domain service IssueManager definition method AssignToAsync to assign the specified Issue to the specified user.
public class IssueManager:DomainService { private readonly IRepository<Issue,Guid> _issueRepository; public IssueManager(IRepository<Issue,Guid> issueRepository) { _issueRepository=issueRepository; } public async Task AssignToAsync(Issue issue,AppUser user) { //Gets the number of open issues for associated users var openIssueCount=await _issueRepository.CountAsync( i=>i.AssingedUserId==user.Id && !i.IsClosed ); //If there are more than 3, an exception is thrown if(openIssueCount>=3) { throw new BusinessException("IssueTracking:ConcurrentOpenIssueLimit"); } issue.AssignedUserId=user.Id; } }
IssueManager injects the required warehouse into the constructor to query the open Issue assigned to the user.
It is recommended to use the Manager suffix to name domain services.
The only problem with this design is: issue Assigneduserid is now public and can be set in any external class. However, it should not be public. The access scope should be internal within the assembly and can only be called in the same assembly (IssueTracking.Domain) project.
This is the solution of this example. We think it is reasonable:
- Domain layer developers are already familiar with domain rules when using IssueManager.
- Application layer developers enforce the use of IssueManager, so entities cannot be modified directly.
Above, we have shown two implementation methods for assigning problems to users. Under the balance of the two methods, we prefer to create domain services when business logic needs to work with external services.
Without a good reason, we don't think it is necessary to create an interface for domain services, such as creating an IIssueManger interface for IssueManager.
application service
Application services are stateless services that implement application use cases. An application service usually uses domain objects to implement use cases, obtain or return data transmission objects DTOs, which are called by the presentation layer.
General principles of application services:
- To realize the application logic of specific use cases, domain logic cannot be realized in application services (the difference between application logic and domain logic needs to be clarified).
- Application service methods cannot return entities because this will break the encapsulation of the domain layer and always only return DTO s.
Example: assigning questions to users
using System; using System.Threading.Tasks; using IssueTracking.Users; using Microsoft.AspNetCore.Authorization; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Repositories; namespace IssueTracking.Issues { 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; } [Authorize] public async Task AssignAsync(IssueAssignDto input) { var issue=await _issueRepository.GetAsync(input.IssueId); var user=await _userRepository.GetAsync(inpu.UserId); await _issueManager.AssignToAsync(issue,user); await _issueRepository.UpdateAsync(issue);//No changes have been made to the issue. Why update it? Status changes were made in IssueManager. } } }
An application service method usually has three steps:
- Get the associated domain object from the database
- Use domain objects (domain services, entities, etc.) to execute business logic
- Update entities in the database (if modified)
When using EF Core at that time, the last Update operation is not necessary and should be stateful change tracking. However, it is recommended to explicitly call to adapt to other database providers.
In the example, issueasigndto is a simple DTO class:
using System; namespace IssueTracking.Issues { public class IssueAssignDto { public Guid IssueId{get;set;} public Guid UserId{get;set;} } }
Learning help
Focusing on the two core technologies of DDD and ABP Framework, a series of articles on core component implementation and comprehensive case implementation will be released later. Please pay attention!
ABP Framework workshop (QQ group: 726299208)
Focus on ABP Framework learning and DDD implementation experience sharing; Sample source code, e-book sharing, welcome to join!