领域建模:做对
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;
}
}
查询:
- 我所做的完全correct/partially正确吗?
- 从性能的角度来看,只是为了创建适当的 class 实例而获取两个对象在未来会成为问题吗?
- 邮件通知是否属于服务层?
- 我的公司存储库如何处理与批准公司的用户相关的 属性?我的公司存储库是否应该引用用户存储库(我认为这是错误的)。
或者,我可以如下编写服务层,但我认为这也不正确。
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)
更好地反映了像 "An admin user approves a company" 这样的用例,但你可以反过来说 "A company is approved by an admin user"。另请注意,您使用的 company.SetIsApproved
方法非常面向 CRUD,当然不能很好地反映您的通用语言。
AR应该设计得越小越好。只要您不创建不必要的大型集群聚合,我认为这不会成为问题。我强烈建议您阅读 Effective Aggregate Design by Vaughn Vernon.
理想情况下,您应该依靠域事件来实现操作的副作用。有很多关于如何在 Web 上实现域事件的信息。但是,由于缺少 publish/subscribe 机制,它可以在应用程序服务层完成,或者您可以在 AR 方法级别注入邮件服务。
问题是您在另一个 AR 中引用了一个 AR。 AR 通常应按身份引用其他 AR。因此,Company
不会保留 AdminUser
,只会保留用户的 ID。通过这样做,您的问题就会消失,并且您可以减小 AR 的大小。
看过 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;
}
}
查询:
- 我所做的完全correct/partially正确吗?
- 从性能的角度来看,只是为了创建适当的 class 实例而获取两个对象在未来会成为问题吗?
- 邮件通知是否属于服务层?
- 我的公司存储库如何处理与批准公司的用户相关的 属性?我的公司存储库是否应该引用用户存储库(我认为这是错误的)。
或者,我可以如下编写服务层,但我认为这也不正确。
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)
更好地反映了像 "An admin user approves a company" 这样的用例,但你可以反过来说 "A company is approved by an admin user"。另请注意,您使用的company.SetIsApproved
方法非常面向 CRUD,当然不能很好地反映您的通用语言。AR应该设计得越小越好。只要您不创建不必要的大型集群聚合,我认为这不会成为问题。我强烈建议您阅读 Effective Aggregate Design by Vaughn Vernon.
理想情况下,您应该依靠域事件来实现操作的副作用。有很多关于如何在 Web 上实现域事件的信息。但是,由于缺少 publish/subscribe 机制,它可以在应用程序服务层完成,或者您可以在 AR 方法级别注入邮件服务。
问题是您在另一个 AR 中引用了一个 AR。 AR 通常应按身份引用其他 AR。因此,
Company
不会保留AdminUser
,只会保留用户的 ID。通过这样做,您的问题就会消失,并且您可以减小 AR 的大小。