Domain driven design 领域建模:正确操作

Domain driven design 领域建模:正确操作,domain-driven-design,solid-principles,domain-data-modelling,Domain Driven Design,Solid Principles,Domain Data Modelling,看过Jimmy Bogard的优秀作品后,我尝试将同样的原则应用到我现有的一个项目中,以评估我对这个概念的掌握程度。下面列出了我的疑问和疑问 域背景:管理员可以查看公司列表。然后他批准了一家公司。数据库应该将布尔字段更新为true,并存储批准该公司的用户的id 最初,我在服务层编写了以下代码。它将请求传递给存储库,存储库更新数据库中的相应字段,然后发送邮件通知 public void ApproveCompany(int companyId, int userId) { _company

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

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

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

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

    //send mail to company representatives on successful approval. 
}
重新分解以创建富域并在域类中封装逻辑,我创建了以下内容

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;
        }
    }
查询:

  • 我所做的完全正确/部分正确吗
  • 从性能的角度来看,获取这两个对象只是为了创建适当的类实例,这在将来会成为一个问题吗
  • 邮件通知是否属于服务层
  • 我的公司存储库将如何处理与批准公司的用户相关的属性?我的公司存储库是否应该引用用户存储库(我认为这是错误的) 或者,我可以像下面那样编写服务层,但我认为这也不正确

    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);
        }
    }
    

    这类问题几乎不可能正确回答,但以下是我所看到的:

  • 这在某种程度上是正确的,但我通常倾向于将行为放在操作所针对的AR上,而不是放在执行该操作的参与者上。在这种情况下,双重分派不会带来任何有用的信息。因此,我将简化为
    company.Approve(adminUser)
    。您可能会说,
    adminUser.approve(Company)
    更好地反映了“管理员用户批准公司”这样的用例,但您可以把它转过来说“公司是由管理员用户批准的”。还要注意,您使用的
    company.SetIsApproved
    方法非常面向CRUD,当然不能很好地反映您无处不在的语言

  • ARs的设计应尽可能小。只要您不创建不必要的大型集群聚合,我认为这不会成为一个问题。我强烈建议你阅读

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

  • 问题是您正在另一个AR中引用AR。AR通常应通过标识引用其他AR。因此,
    Company
    不会仅在用户ID上保留
    AdminUser
    。这样做,您的问题就会消失,AR的大小也会减小


  • 虽然您的问题不适合SO,但这里有一些提示:当您调出数据库中存储的内容时,这不是DDD。存储库仅从持久性保存/加载对象。你不会“创建富域名”,一个域名要么富要么不富。“管理员可以查看”是一个用户界面问题。“管理员可以批准”是一项业务规则。正确的建模从识别概念及其用例开始。DDD中没有“正确”或“错误”的方式,只是正确理解域和DDD模式。@MikeSW说的还有一件事需要补充,那就是掌握域事件。jimmy的演示没有提到这一点,但这可能会启动您的电子邮件通知。