10

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

我有一个名为 Asset(设备)的实体,我将其建模为聚合的根。它有一个标签(属性)列表,用于描述制造商、模型等内容。它存储称为 AssetType 的第二个聚合的标识,该聚合具有一个 TagType 列表,其中一些可以标记为强制性的。

现在在我看来,Asset 的不变条件之一应该引用关联的 AssetType 以在强制标签列表中强制执行非空值。但是我的胆量正在爬上我将如何强制执行一致性的想法。

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

而且 AssetType 也不能很好地进入 Asset 聚合。这似乎同样荒谬。

我的胆量仍然说 Asset 和 AssetType 是两个独立的聚合,但我该如何解决一致性问题?还是我弄错了不变量?

4

2 回答 2

5

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

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

创建资产命令

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

删除标签命令

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

处理这些不变量的另一种方法是引入最终一致性(如果可以接受)。使用这种方法,从资产中删除标签应该发布一个TagRemovedEvent. 然后,此事件的处理程序可以验证未删除强制标记。如果是,它可以创建一个任务或通知,说明资产处于无效状态。请注意,这假设资产处于无效状态是可以接受的,直到某些事情得到纠正。

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

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

查看有效的聚合设计以了解更多信息。

于 2013-04-05T20:45:44.563 回答
4

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

聚合总是可以使用它们自己状态的信息以及它们的命令接收到的参数。

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

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


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

警告 !!!

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

上次我不得不处理类似的结构时,我感到很痛苦。

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

我的不变量错了吗?

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

你永远不应该自己抽象

X持有对 an 实例的引用的类型的实体X-Type几乎总是带有过度抽象的味道,希望重用,这使得模型对业务发展变得僵硬和不灵活。

回答

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

  1. 您可以使用工厂方法创建一个AssetType类,该方法将 aIEnumerable<Tag>转换为 a并在某些标记丢失或意外时抛出TagSet MissingMandatoryTagExceptionUnexpectedTagException
  2. Asset类中,一个命令RegisterTags将接受一个AssetType和一个IEnumerable<Tag>,抛出一个MissingMandatoryTagExceptionWrongAssetTypeException(注意异常对于确保不变量是多么重要)。

编辑
这样的东西,但更多的记录:

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);
    }
}
于 2013-04-05T15:27:14.470 回答