Domain driven design 聚合不变量是否可以包含基于其他地方信息的规则?

Domain driven design 聚合不变量是否可以包含基于其他地方信息的规则?,domain-driven-design,aggregate,modeling,invariants,Domain Driven Design,Aggregate,Modeling,Invariants,在DDD中,聚合不变量是否可以包含基于另一个聚合中的信息的规则?现在我不这么认为,但是这给我带来了一个问题,我不知道如何解决它 我有一个名为资产(设备)的实体,我将其建模为聚合的根。它有一个标签(属性)列表,描述制造商、型号等。它存储称为AssetType的第二个聚合的标识,AssetType有一个标签类型列表,其中一些可以标记为必需的 现在,我觉得Asset的一个不变条件应该引用关联的AssetType,以强制强制强制标记列表中的非空值。但我的内心充满了如何加强一致性的想法 这是否意味着总量真

在DDD中,聚合不变量是否可以包含基于另一个聚合中的信息的规则?现在我不这么认为,但是这给我带来了一个问题,我不知道如何解决它

我有一个名为资产(设备)的实体,我将其建模为聚合的根。它有一个标签(属性)列表,描述制造商、型号等。它存储称为AssetType的第二个聚合的标识,AssetType有一个标签类型列表,其中一些可以标记为必需的

现在,我觉得Asset的一个不变条件应该引用关联的AssetType,以强制强制强制标记列表中的非空值。但我的内心充满了如何加强一致性的想法

这是否意味着总量真的应该包括所有四个实体?如果根目录是AssetType,并且它下面有一个资产列表,那么它可以解决我的问题,但是这不太适合具有其他集合维护不同类型资产列表的核心用例。资产必须是根本,否则我会有问题

资产类型也不能很好地进入资产聚合。这似乎同样荒谬

我的直觉仍然认为Asset和AssetType是两个独立的聚合,但是我如何解决一致性问题呢?还是我的不变量搞错了

聚合不变量是否可以包含基于其他地方信息的规则

聚合始终可以使用其自身状态中的信息以及接收到的参数

有人过去常常通过单例、服务定位器等访问应用程序服务,但在我看来,这是一种紧密耦合的应用程序的味道。他们忘记了方法的参数是有效的依赖关系注入器!:-)

在DDD中,聚合不变量是否可以包含基于另一个聚合中的信息的规则

否。
当然,除非第二个聚合是通过命令的参数提供的

警告

我有一个名为资产(设备)的实体。
... (和a)第二个聚合称为AssetType

上一次我不得不处理类似的结构,那是一种痛苦

很可能您选择了错误的抽象

我的不变量搞错了吗

也许。。。您是否向领域专家询问过?他是否谈论“标记类型”

持有对
X-type
实例引用的
X-type
类型的实体几乎总是一种过度抽象的味道,为了重用,这使得模型变得僵化且不灵活,无法适应业务发展

回答

如果(且仅当)领域专家实际用这些术语描述了模型,则可能的方法如下:

  • 您可以使用工厂方法创建一个
    AssetType
    类,该工厂方法将
    IEnumerable
    转换为
    TagSet
    MissingMandatoryTagException
    UnexpectedTageException
    ,如果某些标记丢失或意外
  • Asset
    类中,命令
    RegisterTags
    将接受
    AssetType
    IEnumerable
    ,抛出
    MissingMandatoryTagException
    ErrorAssetTypeException
    (注意异常对于确保不变量有多重要)
  • 编辑
    类似于此,但有更多的文档记录:

    公共类资产类型
    {
    专用只读词典_tagTypes=new Dictionary();
    公共资产类型(资产类型名称)
    {
    //在这里验证。。。
    名称=名称;
    }
    /// 
    ///允许将标记类型指定给此类型的资产。
    /// 
    /// 
    public void EnableTagType(标记类型)
    {
    //在这里验证。。。
    _tagTypes[type]=false;
    }
    /// 
    ///要求为此类型的任何资产定义标记类型。
    /// 
    /// 
    public void RequireTagType(标记类型)
    {
    //在这里验证。。。
    _tagTypes[type]=false;
    }
    公共资产类型名称{get;private set;}
    /// 
    ///生成标记集。
    /// 
    ///标签。
    ///当前资产类型的一组标记。
    ///为null或空。
    ///至少需要一个标签
    ///中缺少按当前资产类型列出的。
    ///至少有一个
    ///不允许用于当前资产类型。
    /// 
    公共标记集BuildTagSet(IEnumerable标记)
    {
    if(null==tags | | tags.Count()==0)
    抛出新的ArgumentNullException(“标记”);
    标记集标记集=新标记集();
    foreach(标记中的标记)
    {
    if(!\u tagTypes.ContainsKey(tag.Key))
    {
    string message=string.Format(“不能在资产类型{1}中使用标记{0}。”,tag.Key,Name);
    抛出新的意外异常(“tags”、tag.Key、message);
    }
    tagSet.Add(tag);
    }
    foreach(tagTypes中的TagType标记类型,其中(kvp=>kvp.Value==true)。选择(kvp=>kvp.Key))
    {
    如果(!tagSet.Any(t=>t.Key.Equals(tagType)))
    {
    string message=string.Format(“必须向类型为{1}的资产提供标记{0}。”,tagType,Name);
    抛出新的MissingMandatoryTagException(“标记”、标记类型、消息);
    }
    }
    返回标记集;
    }
    }
    公共类资产
    {
    公共资产(资产名称、资产类型名称类型)
    {
    //在这里验证。。。
    名称=名称;
    类型=类型;
    }
    公共标记集标记{get;private set;}
    公共资产名称{get;private set;}
    公共资产类型名称类型{get;private set;}
    /// 
    ///注册标签。
    /// 
    ///标记的类型。
    ///Th