在DDD中,聚合不变量包括基于另一个聚合中的信息的规则吗?现在我不这么认为,但是这会给我带来麻烦,我不知道如何解决它。
我有一个名为Asset(equipment)的实体,我将其建模为聚合的根。它有一个标签(属性)列表,用于描述制造商,模型等等。它存储名为AssetType的第二个聚合的标识,其中包含TagType列表,其中一些可以标记为必需。
现在在我看来,Asset的一个不变条件应该引用相关的AssetType,以在强制标记列表中强制执行非空值。但是,我的想法是如何强制执行一致性。
这是否意味着聚合应该真正包含所有四个实体?如果根目录是AssetType并且它下面有一个Assets列表,它可以解决我的问题,但是这不适合核心用例,其中包含维护不同类型资产列表的其他聚合。资产真的必须是根,否则我会遇到问题。
AssetType也不能很好地进入Asset聚合。这似乎是荒谬的。
我的胆量仍然说资产和资产类型是两个单独的聚合,但我如何解决一致性问题?或者我有不变的错误?
答案 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 强>
如果(且仅当)域专家实际上用这些术语描述了模型,可能的方法如下:
AssetType
课程,将IEnumerable<Tag>
转换为TagSet
and throws MissingMandatoryTagException
或UnexpectedTagException
一些标签丢失或意外。 Asset
课程中,命令RegisterTags
会接受AssetType
和IEnumerable<Tag>
,抛出MissingMandatoryTagException
和WrongAssetTypeException
(注意确保不变量的例外有多重要。) 修改强>
这样的事情,但更多的记录:
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);
}
}