Domain driven design DDD和定义聚合

Domain driven design DDD和定义聚合,domain-driven-design,aggregateroot,ddd,Domain Driven Design,Aggregateroot,Ddd,我们正在建立一个系统,可以向多家公司销售我们的api服务。 我们有 公司(购买我们api的公司) 帐户(每个公司可以有多个帐户,每个帐户都有自己的用户类型) 用户(帐户内的用户) 在基础设施方面,它看起来像这样: "company1" : { "accounts" : [ account1 :{"users" : [{user1,user2}], accountType}, account2 :{"users" : [{user1,user2}], acc

我们正在建立一个系统,可以向多家公司销售我们的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();

  }

}
类用户帐户{
国际帐户ID;
字符串AccountName;
int AccountTypeId;
列出可用的ISAccount用户类型
用户用户
void SetUserType(userTypeId){
if(AvailableUserTypesForIsAccount.Contains(userTypeId)==false)
抛出新的NotSupportedException();
}
}
使用此聚合,我们可以更改用户的类型,但它只能是该帐户可用的类型(不变量之一)

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


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

理解骨料的设计权衡很重要;由于聚合将域模型划分为独立的空间,因此您可以同时修改模型的不相关部分。但是,您失去了在变更点实施跨多个聚合的业务规则的能力

这意味着你需要清楚地理解这两件事的商业价值。对于不会经常更改的实体,您的业务可能更喜欢严格执行而不是并发更改;当数据经常更改时,您可能会选择更隔离的方式

实际上,隔离意味着评估业务是否能够减轻“冲突”编辑使模型处于不满意状态的情况

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

对于这样一个不变量,一个重要的问题是“这里失败的商业成本是多少?”

如果
User
Account
是单独的聚合,那么您将面临一个问题,即在某个帐户放弃对该类型的支持的同时,用户被分配到一个“类型”。那么,检测(变更后)违反“不变量”的情况发生的成本是多少,应用更正的成本是多少

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

根据我的理解,我的域模型应该称为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();

  }

}
我想你已经失去了这里的情节。“域模型”实际上不是一个命名的东西,它只是一个集合

如果您想要一个包含用户和用户类型的帐户聚合,那么您可能会对它进行如下建模

Account : Aggregate {
    accountId : Id<Account>,
    name : AccountName,
    users : List<User>,
    usertypes : List<UserType>
}
Account : Aggregate {
    accountId : Id<Account>,
    name : AccountName,
    users : Map<Id<User>,UserType>
    usertypes : List<UserType>
}
当我从存储库中获取UserAccount时,我将获取所有必要的表(或实体数据对象),并将它们映射到聚合,然后作为一个整体返回它

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

Account::SetUserType(UserHint hint, UserType userTypeId){
    if(! usertypes.Contains(userTypeId)) {
        throw new AccountInvariantViolationException();
    }
    User u = findUser(users, hint);
    ...
}
让Account和User之间的关系以及用户类型(作为AccountUser实体)只存在于Account聚合中,而让其余的用户信息存在于单独的用户聚合中,怎么样

该模型可能适用于某些类型的问题——在这种情况下,帐户合计可能看起来像

Account : Aggregate {
    accountId : Id<Account>,
    name : AccountName,
    users : List<User>,
    usertypes : List<UserType>
}
Account : Aggregate {
    accountId : Id<Account>,
    name : AccountName,
    users : Map<Id<User>,UserType>
    usertypes : List<UserType>
}
科目:合计{
accountId:玩具问题的Id真的很难)

原则是理解必须始终保持的业务不变量(与可接受后期对账的业务不变量相反),然后将必须保持一致以满足不变量的所有状态组合在一起

但若这个账户可以有成百上千的用户呢?你们对聚合的愿景是什么

假设相同的约束条件:我们有一些聚合负责允许的用户类型范围……如果聚合太大,无法以合理的方式进行管理,并且业务施加的约束无法放松,那么我可能会破坏“存储库”并允许集合验证规则的实施泄漏到数据库本身


DDD的骄傲来自其最初的OO最佳实践根源,它认为模型是真实的,持久性存储只是一个环境细节。但从实际的角度来看,在一个进程有生命周期、存在竞争消费者的世界中……持久性存储代表了业务的真相ess.

我们之前也谈到过类似的事情,但如何在帐户聚合中只保留帐户和用户之间的关系以及用户类型(作为
AccountUser
实体)还有,其他用户信息是否存在于一个单独的
user
aggregate中?这可能是一种保持规则强一致性同时最小化争用的方法,只要