DDD和定义聚合

时间:2016-10-24 14:17:03

标签: domain-driven-design aggregateroot

我们正在建立一个可以向多家公司出售我们的api服务的系统。 我们有

  • 公司(购买我们api的公司)
  • 帐户(每个公司可以有多个帐户,每个帐户都有自己的用户类型)
  • 用户(帐户内的用户)

在基础设施方面,它看起来像这样:

"company1" : {
    "accounts" : [
       account1 :{"users" : [{user1,user2}], accountType},
       account2 :{"users" : [{user1,user2}], accountType},
]}

其中一项业务规则规定用户在注册后无法更改帐户。 其他规则规定用户可以更改其类型,但仅限于该帐户类型。

根据我的理解,我的域模型应该称为UserAccount,它应该包含Account,User和UserType实体,其中Account是聚合根。

class UserAccount{
  int AccountId;
  string AccountName;
  int AccountTypeId;
  List<UserTypes> AvailableUserTypesForThisAccount
  User User
  void SetUserType(userTypeId){
      if(AvailableUserTypesForThisAccount.Contains(userTypeId) == false)
         throw new NotSupportedException();

  }

}

使用此聚合,我们可以更改用户的类型,但它只能是该帐户可用的类型(不变量之一)。

当我从存储库中获取UserAccount时,我将获取所有必需的表(或实体数据对象)并将它们映射到聚合,并将其作为一个整体返回。

我的理解和建模是否朝着正确的方向发展?

1 个答案:

答案 0 :(得分:4)

理解聚合体的设计权衡非常重要;因为聚合将您的域模型划分为独立空间,所以您可以同时修改模型的不相关部分。但是,您无法在更改时执行跨越多个聚合的业务规则。

这意味着您需要清楚地了解这两件事的商业价值。对于不经常更改的实体,您的企业可能更喜欢严格执行并发更改;如果数据经常发生变化,您可能最终会选择更多隔离。

在实践中,隔离意味着评估企业是否有能力减轻“冲突”的情况。编辑使模型处于令人不满意的状态。

  

使用此聚合,我们可以更改用户的类型,但它只能是该帐户可用的类型(不变量之一)。

对于这样的不变量,一个重要的问题是&#34;这里失败的商业成本是什么?&#34;

如果UserAccount是单独的聚合,那么您将面临用户被分配到&#34;类型&#34;同时,一个帐户正在放弃对该类型的支持。那么,在(更改后)检测到违反&#34;不变量&#34;已经发生了,应用修正需要多少钱?

如果Account相对稳定(似乎很可能),那么通过将用户类型与帐户中允许的缓存列表进行比较,可以减轻大多数错误。可以在更改用户时或在支持编辑的UI中评估此缓存。这将减少(但不会消除)错误率而不会影响并发编辑。

  

根据我的理解,我的域模型应该称为UserAccount,它应该包含Account,User和UserType实体,其中Account是聚合根。

我认为你在这里失去了阴谋。 &#34;域模型&#34;它不是一个真正的名字,它只是一个聚合的集合。

如果您想要一个包含用户和用户类型的帐户聚合,那么您可能会将其模型化为

Account : Aggregate {
    accountId : Id<Account>,
    name : AccountName,
    users : List<User>,
    usertypes : List<UserType>
}

此设计意味着需要通过帐户聚合访问用户的所有更改,并且没有用户属于多个帐户,并且没有其他聚合可以直接引用用户(您需要直接与用户协商)帐户汇总)。

Account::SetUserType(UserHint hint, UserType userTypeId){
    if(! usertypes.Contains(userTypeId)) {
        throw new AccountInvariantViolationException();
    }
    User u = findUser(users, hint);
    ...
}
  

当我从存储库中获取UserAccount时,我将获取所有必需的表(或实体数据对象)并将它们映射到聚合,并将其作为一个整体返回。

是的,这是完全正确的 - 这是我们通常更喜欢松散耦合的聚合而不是一个大聚合的另一个原因。

  

如果只将帐户和用户之间的关系置于帐户聚合中以及用户类型(作为AccountUser实体),并将其余用户信息置于单独的用户聚合中?

该模型可以解决某些问题 - 在这种情况下,帐户聚合可能看起来像

Account : Aggregate {
    accountId : Id<Account>,
    name : AccountName,
    users : Map<Id<User>,UserType>
    usertypes : List<UserType>
}

此设计允许您在某些用户当前属于该类型时尝试从帐户中删除UserType时抛出异常。但是,例如,它不能确保此处描述的用户类型实际上与独立用户聚合的状态一致 - 或者确定所识别的用户存在的事件(您将依赖于那些用户的检测和缓解)例)。

这样更好吗?更差?如果不对所解决的实际问题有更透彻的了解(尝试从玩具问题中理解非常困难),这是不可能的。

原则是要了解哪些必须始终保持的业务不变量(与后来的和解可接受的那些相对应),然后将所有必须保持一致的状态组合在一起以满足不变量。< / p>

  

但是如果帐户可以拥有数百或数千个用户呢?您对聚合的看法是什么?

假设相同的约束:我们有一些聚合负责允许的用户类型范围....如果聚合太大而无法以合理的方式进行管理,并且业务所施加的约束不能放松,然后我可能会妥协&#34;存储库&#34;抽象,并允许执行设置验证规则泄漏到数据库本身。

DDD的设想源自其最初的OO最佳实践根源,即模型是真实的,而持久性存储只是一个环境细节。但是从实际的角度来看,在一个过程具有生命周期并且存在竞争消费者的世界中......它是代表事业真相的持久性商店。