Может ли агрегатный инвариант включать правило, основанное на информации из других источников?
Агрегаты всегда могут использовать информацию в своих собственных состояниях и аргументы, которые получают их команды. .
Кто-то использует для доступа к аппликативным сервисам через синглтоны, локаторы сервисов и так далее, но, по-моему, это запах тесно связанных приложений. Они забывают, что аргументы методов являются эффективными инъекторами зависимостей! :-)
Может ли инвариант агрегатов в DDD включать правило, основанное на информации из другого агрегата?
Нет.
За исключением случаев, когда второй агрегат предоставляется через аргументы команд, конечно.
ВНИМАНИЕ! ! !
У меня есть объект с именем Asset (оборудование)...
... (и) второй агрегат с именем AssetType...
В последний раз, когда мне приходилось справляться с подобной структурой, это была боль.
Скорее всего, вы выбираете неправильные абстракции.
я неправильно понял свой инвариант?
Вероятно... Вы обращались к эксперту домена? он говорит о "TagTypes"?
Вы никогда не должны абстрагироваться самостоятельно.
Сущности типа X
, содержащие ссылку на экземпляр X-Type
, почти всегда пахнут чрезмерной абстракцией, которая в надежде на повторное использование делает модель жесткой и негибкой для развития бизнеса.
ОТВЕТ
Если (и только если) эксперт в предметной области действительно описал модель в этих терминах, возможен следующий подход:
- вы можете создать класс
AssetType
с фабричным методом, который превращает IEnumerable<Tag>
в TagSet
language.html" rel="nofollow">и выдает либо 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);
}
}
person
Giacomo Tesio
schedule
05.04.2013