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

时间:2013-04-05 13:46:05

标签: domain-driven-design aggregate modeling invariants

在DDD中,聚合不变量包括基于另一个聚合中的信息的规则吗?现在我不这么认为,但是这会给我带来麻烦,我不知道如何解决它。

我有一个名为Asset(equipment)的实体,我将其建模为聚合的根。它有一个标签(属性)列表,用于描述制造商,模型等等。它存储名为AssetType的第二个聚合的标识,其中包含TagType列表,其中一些可以标记为必需。

现在在我看来,Asset的一个不变条件应该引用相关的AssetType,以在强制标记列表中强制执行非空值。但是,我的想法是如何强制执行一致性。

这是否意味着聚合应该真正包含所有四个实体?如果根目录是AssetType并且它下面有一个Assets列表,它可以解决我的问题,但是这不适合核心用例,其中包含维护不同类型资产列表的其他聚合。资产真的必须是根,否则我会遇到问题。

AssetType也不能很好地进入Asset聚合。这似乎是荒谬的。

我的胆量仍然说资产和资产类型是两个单独的聚合,但我如何解决一致性问题?或者我有不变的错误?

2 个答案:

答案 0 :(得分:5)

在这种情况下,有几种强制执行不变量的方法。

首先,考虑Asset聚合周围的行为。我假设至少有一个CreateAssetCommand和一个RemoveTagCommand。在执行这些命令期间,应按以下方式强制执行不变量:

<强> CreateAssetCommand

由于资产始终与资产类型相关联,因此必须在此命令中提供AssetTypeId。呼叫者必须通过查找特定资产类型来获取此ID。查找AssetType时,也可以检索相应的TagType实体,特别是强制实体。这将允许调用者构造所需的Tag实例作为命令的一部分发送。请注意,调用者有责任提供有效的资产类型和标记。

<强> RemoveTagCommand

此命令的处理程序可以检索存储Asset的相应AssetTypeId。接下来,处理程序检索资产类型的强制标记集,并确保不删除这些标记。在这种情况下,不变量由处理程序本身强制执行。

处理这些不变量的另一种方法是引入eventual consistency,如果可以接受的话。使用此方法,从资产中删除标记应发布TagRemovedEvent。然后,此事件的处理程序可以验证是否未删除强制标记。如果是,则可以创建任务或通知,指出资产处于无效状态。请注意,这假定在更正某些内容之前资产处于无效状态是可以接受的。

现在围绕AssetType的行为。一个可能损害Asset聚合完整性的命令是引入新的强制Tag。在这种情况下,确保完整性的onyl方法是为每个相应的资产创建适当的标签。由于这可能无法自动完成,因此必须接受最终的一致性,直到通过人工干预提供适当的标签。

通过所有这些方法,您无法获得RDMS所具有的完整性。强制执行交叉聚合不变量的责任委托给命令处理程序,事件处理程序和调用代码。但是,在许多情况下,这种一致性是完全可以接受的。

请查看Effective Aggregate Design了解更多信息。

答案 1 :(得分:4)

  

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

聚合总是可以使用自己状态中的信息以及commands收到的参数。

有人通过单身人士,服务定位器等来访问应用服务,但IMO,这是紧密耦合的应用程序的气味。他们忘记了方法的参数是有效的依赖注入器! : - )

  

在DDD中,聚合不变量包括基于另一个聚合中的信息的规则吗?

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

警告! ! !

  

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

最后一次我必须应对类似的结构,这是一种痛苦。

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

  

我有不变的错误吗?

可能......你问过领域专家吗? 是否谈到“TagTypes”?

You should never abstract on your own

持有对X实例的引用的类型X-Type的实体几乎总是过度抽象的气味,希望重用,使模型对业务进化僵化且不灵活

<强> ANSWER

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

  1. 您可以使用工厂方法创建AssetType课程,将IEnumerable<Tag>转换为TagSet and throws MissingMandatoryTagExceptionUnexpectedTagException一些标签丢失或意外。
  2. Asset课程中,命令RegisterTags会接受AssetTypeIEnumerable<Tag>,抛出MissingMandatoryTagExceptionWrongAssetTypeException(注意确保不变量的例外有多重要。)
  3. 修改
    这样的事情,但更多的记录:

    public class AssetType
    {
        private readonly Dictionary<TagType, bool> _tagTypes = new Dictionary<TagType, bool>();
        public AssetType(AssetTypeName name)
        {
            // validation here... 
            Name = name;
        }
    
        /// <summary>
        /// Enable a tag type to be assigned to asset of this type.
        /// </summary>
        /// <param name="type"></param>
        public void EnableTagType(TagType type)
        {
            // validation here... 
            _tagTypes[type] = false;
        }
    
        /// <summary>
        /// Requires that a tag type is defined for any asset of this type.
        /// </summary>
        /// <param name="type"></param>
        public void RequireTagType(TagType type)
        {
            // validation here... 
            _tagTypes[type] = false;
        }
    
        public AssetTypeName Name { get; private set; }
    
    
        /// <summary>
        /// Builds the tag set.
        /// </summary>
        /// <param name="tags">The tags.</param>
        /// <returns>A set of tags for the current asset type.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="tags"/> is <c>null</c> or empty.</exception>
        /// <exception cref="MissingMandatoryTagException">At least one of tags required 
        /// by the current asset type is missing in <paramref name="tags"/>.</exception>
        /// <exception cref="UnexpectedTagException">At least one of the <paramref name="tags"/> 
        /// is not allowed for the current asset type.</exception>
        /// <seealso cref="RequireTagType"/>
        public TagSet BuildTagSet(IEnumerable<Tag> tags)
        {
            if (null == tags || tags.Count() == 0)
                throw new ArgumentNullException("tags");
            TagSet tagSet = new TagSet();
    
            foreach (Tag tag in tags)
            {
                if(!_tagTypes.ContainsKey(tag.Key))
                {
                    string message = string.Format("Cannot use tag {0} in asset type {1}.", tag.Key, Name);
                    throw new UnexpectedTagException("tags", tag.Key, message);
                }
                tagSet.Add(tag);
            }
    
            foreach (TagType tagType in _tagTypes.Where(kvp => kvp.Value == true).Select(kvp => kvp.Key))
            {
                if(!tagSet.Any(t => t.Key.Equals(tagType)))
                {
                    string message = string.Format("You must provide the tag {0} to asset of type {1}.", tagType, Name);
                    throw new MissingMandatoryTagException("tags", tagType, message);
                }
            }
    
            return tagSet;
        }
    }
    
    public class Asset
    {
        public Asset(AssetName name, AssetTypeName type)
        {
            // validation here... 
            Name = name;
            Type = type;
        }
    
        public TagSet Tags { get; private set; }
    
        public AssetName Name { get; private set; }
    
        public AssetTypeName Type { get; private set; }
    
        /// <summary>
        /// Registers the tags.
        /// </summary>
        /// <param name="tagType">Type of the tag.</param>
        /// <param name="tags">The tags.</param>
        /// <exception cref="ArgumentNullException"><paramref name="tagType"/> is <c>null</c> or
        /// <paramref name="tags"/> is either <c>null</c> or empty.</exception>
        /// <exception cref="WrongAssetTypeException"><paramref name="tagType"/> does not match
        /// the <see cref="Type"/> of the current asset.</exception>
        /// <exception cref="MissingMandatoryTagException">At least one of tags required 
        /// by the current asset type is missing in <paramref name="tags"/>.</exception>
        /// <exception cref="UnexpectedTagException">At least one of the <paramref name="tags"/> 
        /// is not allowed for the current asset type.</exception>
        public void RegisterTags(AssetType tagType, IEnumerable<Tag> tags)
        {
            if (null == tagType) throw new ArgumentNullException("tagType");
            if (!tagType.Name.Equals(Type))
            {
                string message = string.Format("The asset {0} has type {1}, thus it can not handle tags defined for assets of type {2}.", Name, Type, tagType.Name);
                throw new WrongAssetTypeException("tagType", tagType, message);
            }
            Tags = tagType.BuildTagSet(tags);
        }
    }