领域建模:做对

Domain modelling: Doing it right

看过 Jimmy Bogard 的优秀 video on crafting wicked domains 后,我尝试将相同的原则应用到我现有的一个项目中,以评估我对这个概念的掌握程度。下面列出了我的疑问和疑问。

域背景:管理员可以查看公司列表。然后他批准了一家公司。数据库应该将布尔字段更新为 true 并存储批准公司的用户的 ID。

最初,我在服务层中编写了以下代码。它将请求传递给更新数据库中相应字段的存储库,然后发送邮件通知。

public void ApproveCompany(int companyId, int userId)
{
    _companyRep.ApproveCompany(companyId, userId);

    //send mail to company representatives on successful approval. 
}

重构以创建丰富的域并在域内封装逻辑 class,我创建了以下内容。

public void ApproveCompany(int companyId, int userId)
    {
        var user = _userRep.GetById(userId);
        var company = _companyRep.GetById(companyId);
        user.Approve(company);

        _companyRep.Insert(c);

        //send mail to company representatives on successful approval. 
    }

public class AdminUser
    {
        public string Name { get; set; }

        public void Approve(MyApprovedCompany c)
        {
            c.SetIsApproved(this);
        }       
    }

public class Company 
    {
        public bool IsApproved { get; private set; }
        public AdminUser ApprovedBy { get; private set; }

        public void SetIsApproved(AdminUser user)
        {
            if (this.IsApproved)
                throw new Exception("This company has already been approved by user: " + user.Name);

            this.IsApproved = true;
            this.ApprovedBy = user;
        }
    }

查询:

  1. 我所做的完全correct/partially正确吗?
  2. 从性能的角度来看,只是为了创建适当的 class 实例而获取两个对象在未来会成为问题吗?
  3. 邮件通知是否属于服务层?
  4. 我的公司存储库如何处理与批准公司的用户相关的 属性?我的公司存储库是否应该引用用户存储库(我认为这是错误的)。

或者,我可以如下编写服务层,但我认为这也不正确。

public void ApproveCompany(int companyId, int userId)
{
    var user = _userRep.GetById(userId);
    var company = _companyRep.GetById(companyId);

    if(company.IsApproved)
    {
        throw new Exception("This company has already been approved by user: " + _userRep.GetById(company.ApprovedUserId).Name);        
    }
    else
    {
        user.Approve(company);
        _companyRep.Insert(c);
    }
}

这类问题几乎不可能正确回答,但是我可以根据我的所见告诉你:

  1. 有点正确,但我通常更喜欢将行为放在操作所针对的 AR 上,而不是演员执行它。恕我直言,在这种情况下,双重调度不会带来任何有用的东西。因此,我会简化为company.Approve(adminUser)。你可能会说 adminUser.approve(Company) 更好地反映了像 "An admin user approves a company" 这样的用例,但你可以反过来说 "A company is approved by an admin user"。另请注意,您使用的 company.SetIsApproved 方法非常面向 CRUD,当然不能很好地反映您的通用语言。

  2. AR应该设计得越小越好。只要您不创建不必要的大型集群聚合,我认为这不会成为问题。我强烈建议您阅读 Effective Aggregate Design by Vaughn Vernon.

  3. 理想情况下,您应该依靠域事件来实现操作的副作用。有很多关于如何在 Web 上实现域事件的信息。但是,由于缺少 publish/subscribe 机制,它可以在应用程序服务层完成,或者您可以在 AR 方法级别注入邮件服务。

  4. 问题是您在另一个 AR 中引用了一个 AR。 AR 通常应按身份引用其他 AR。因此,Company 不会保留 AdminUser,只会保留用户的 ID。通过这样做,您的问题就会消失,并且您可以减小 AR 的大小。