In DDD can an aggregates invariant include a rule based on information in a another aggregate? Now I don\'t think so, however this causes me a problem and I don\'t know how to
Can an aggregates invariant include a rule based on information from elsewhere?
Aggregates can always use the informations in their own states and the argument that their commands recieves.
Someone use to access applicative services via singletons, service locators and so on, but IMO, that's a smell of tightly coupled applications. They forget that methods' arguments are effective dependency injectors! :-)
In DDD can an aggregates invariant include a rule based on information in a another aggregate?
No.
Except if the second aggregate is provided via commands' arguments, of course.
WARNING ! ! !
I have an entity called Asset (equipment)...
... (and a) second aggregate called AssetType...
The last time that I had to cope with a similar structure, it was a pain.
Chances are that you are choosing the wrong abstractions.
have I got my invariant wrong?
Probably... Did you asked to the domain expert? Does he talks about "TagTypes"?
You should never abstract on your own.
Entities of type X
holding a reference to an instance of an X-Type
are almost always a smell of over-abstraction, that in the hope of reuse, makes the model rigid and inflexible to business evolution.
ANSWER
If (and only if) the domain expert actually described the model in these terms, a possible approach is the following:
AssetType
class with a factory method that turns an IEnumerable
into a TagSet
and throws either MissingMandatoryTagException
or UnexpectedTagException
if some of the tag is missing or unexpected. Asset
class, a command RegisterTags
would accept an AssetType
and an IEnumerable
, throwing the MissingMandatoryTagException
and WrongAssetTypeException
(note how important are exceptions to ensure invariants).edit
something like this, but much more documented:
public class AssetType
{
private readonly Dictionary _tagTypes = new Dictionary();
public AssetType(AssetTypeName name)
{
// validation here...
Name = name;
}
///
/// Enable a tag type to be assigned to asset of this type.
///
///
public void EnableTagType(TagType type)
{
// validation here...
_tagTypes[type] = false;
}
///
/// Requires that a tag type is defined for any asset of this type.
///
///
public void RequireTagType(TagType type)
{
// validation here...
_tagTypes[type] = false;
}
public AssetTypeName Name { get; private set; }
///
/// Builds the tag set.
///
/// The tags.
/// A set of tags for the current asset type.
/// is null or empty.
/// At least one of tags required
/// by the current asset type is missing in .
/// At least one of the
/// is not allowed for the current asset type.
///
public TagSet BuildTagSet(IEnumerable 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; }
///
/// Registers the tags.
///
/// Type of the tag.
/// The tags.
/// is null or
/// is either null or empty.
/// does not match
/// the of the current asset.
/// At least one of tags required
/// by the current asset type is missing in .
/// At least one of the
/// is not allowed for the current asset type.
public void RegisterTags(AssetType tagType, IEnumerable 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);
}
}