实现领域驱动设计 - 使用ABP框架 - 领域服务
领域服务
领域服务实现领域逻辑
- 依赖于服务和存储库。
- 需要处理多个聚合,因为该逻辑不适合任何聚合。
领域服务与领域对象一起工作。它们的方法可以获取并返回实体、值对象、原始类型……但是,它们不获取/返回dto。dto是应用层的一部分
示例:分配问题给用户
记住问题分配是如何在问题实体中实现的
public class Issue : AggregateRoot<Guid>
{
public Guid? AssignedUserId { get; private set; }
public async Task AssignToAsync(AppUser user, IUserIssueService userIssueService)
{
var openIssueCount = await userIssueService.GetOpenIssueCountAsync(user.Id);
if(openIssueCount >= 3)
{
throw new BusinessException("IssueTracking:ConcurrentOpenIssueLimit");
}
AssignedUserId = user.Id;
}
public void CleanAssignment()
{
AssignedUserId = null;
}
}
在这里,我们将把这个逻辑转移到领域服务中。
首先,修改Issue类:
public class Issue : AggregateRoot<Guid>
{
public Guid? AssignedUserId { get; internal set; }
}
- 删除了与分配问题相关的方法。
- 将 AssignedUserId 属性的setter从私有改为内部,以允许从领域服务设置它
下一步是创建一个名为 IssueManager 的领域服务,该服务具有AssignToAsync 来将给定问题分配给给定用户
public class IssueManager : DomainService
{
private readonly IRepository<Issue, Guid> _issueReposiroty;
public IssueManager(IRepository<Issue, Guid> issueReposiroty)
{
_issueReposiroty = issueReposiroty;
}
public async Task AssignToAsync(Issue issue, AppUser user)
{
var openIssueCount = await _issueRepository.CountAsync(i => i.AssignedUserId == user.Id && !i.IsClosed);
if(openIssueCount >= 3)
{
throw new BusinessException("IssueTracking:ConcurrentOpenIssueLimit");
}
issue.AssignedUserId = user.Id;
}
}
IssueManager 可以注入任何依赖的服务,并用于查询用户的分配问题数量。
我们更喜欢并建议为领域服务使用 Manager 后缀。
这个设计的唯一问题就是 Issue.AssignedUserId
现在在类外开放设置了。然而,它不是公开的。它是内部的,并且只能在同一个程序集中(IssueTracking)进行更改。此示例解决方案的领域项目。我们认为这是合理的
- 领域层开发人员已经知道领域规则,他们使用 IssueManager
- 应用层开发人员已经被强制使用IssueManager,他们不直接设置它。
虽然两种方法之间存在权衡,但当业务逻辑需要使用外部服务时,我们更喜欢创建域服务
如果你没有一个好的理由,我们认为没有必要为领域服务创建接口(比如为IssueManager创建IIssueManager)