实现领域驱动设计 - 使用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)

posted @ 2022-06-23 16:24  Broadm  阅读(190)  评论(2编辑  收藏  举报