2013-04-05 16 views
9

In DDD kann eine Aggregat-Invariante eine Regel enthalten, die auf Informationen in einem anderen Aggregat basiert? Jetzt denke ich nicht, aber das verursacht mir ein Problem und ich weiß nicht, wie ich es lösen soll.Kann eine Aggregationsinvariante eine Regel basierend auf Informationen von anderswo enthalten?

Ich habe eine Entität namens Asset (Ausrüstung), die ich als Wurzel eines Aggregats modelliere. Es enthält eine Liste von Tags (Eigenschaften), die Dinge wie Hersteller, Modell usw. beschreiben. Es speichert die Identität des zweiten Aggregats namens AssetType, das eine Liste von TagTypes enthält, von denen einige als obligatorisch gekennzeichnet werden können.

Jetzt scheint es mir, dass eine der invarianten Bedingungen für Asset einen Verweis auf den zugeordneten AssetType machen sollte, um Nicht-Null-Werte in der Liste der obligatorischen Tags zu erzwingen. Aber meine Gedanken krümmen sich mit dem Gedanken, wie ich Konsistenz durchsetzen werde.

Bedeutet dies, dass das Aggregat wirklich alle vier Entitäten umfassen sollte? Wenn der Stamm AssetType wäre und eine Liste von Assets darunter hätte, könnte er mein Problem lösen, aber dies wird nicht sehr gut zu einem Core-Anwendungsfall passen, bei dem andere Aggregate Listen verschiedener Asset-Typen verwalten. Asset muss wirklich die Wurzel sein sonst werde ich Probleme haben.

Und AssetType kann auch nicht sehr gut in das Asset-Aggregat gehen. Das erscheint genauso absurd.

Mein Eingeweide sagt immer Asset und AssetType sind zwei separate Aggregate, aber wie behebe ich die Konsistenzprobleme? Oder habe ich meine Invariante falsch?

Antwort

4

In diesem Szenario gibt es mehrere Methoden für Invarianten durchzusetzen.

Betrachten Sie zuerst die Verhaltensweisen um das Asset Aggregat. Ich nehme an, es gibt mindestens eine CreateAssetCommand und eine RemoveTagCommand. Die Invarianten sollten während der Ausführung dieser Befehle in der folgenden Art und Weise durchgesetzt werden:

CreateAssetCommand

Da ein Vermögenswert immer im Zusammenhang mit einem Asset-Typ, ein AssetTypeId muß als Teil dieses Befehls zur Verfügung gestellt werden. Diese ID muss vom Aufrufer abgerufen werden, möglicherweise durch Nachschlagen eines bestimmten Asset-Typs. Wenn AssetType nachgeschlagen wird, können die entsprechenden TagType Entitäten auch abgerufen werden, die obligatorischen insbesondere. Dadurch kann der Aufrufer die erforderlichen Tag Instanzen erstellen, die als Teil des Befehls gesendet werden. Hinweis: Es liegt in der Verantwortung des Aufrufers, einen gültigen Inhaltstyp und Tags anzugeben.

RemoveTagCommand

Der Handler für diesen Befehl kann die entsprechende Asset abrufen, die die AssetTypeId speichert. Als Nächstes ruft der Handler die obligatorischen Tags für den Asset-Typ ab und stellt sicher, dass diese Tags nicht entfernt werden. In diesem Fall wird die Invariante vom Handler selbst erzwungen.

Eine andere Möglichkeit, um diese Invarianten zu behandeln, ist die Einführung eventual consistency, wenn akzeptabel. Bei diesem Ansatz sollte das Entfernen eines Tags aus einem Asset einen TagRemovedEvent veröffentlichen. Ein Handler für dieses Ereignis kann dann überprüfen, dass ein obligatorisches Tag nicht entfernt wurde. Wenn dies der Fall ist, kann eine Aufgabe oder eine Benachrichtigung erstellt werden, die angibt, dass sich ein Asset in einem ungültigen Status befindet. Beachten Sie, dass davon ausgegangen wird, dass es akzeptabel ist, dass ein Asset in einem ungültigen Zustand ist, bis etwas korrigiert wird.

Jetzt zu Verhaltensweisen um AssetType. Ein Befehl, der die Integrität des Asset Aggregats beeinträchtigen könnte, ist die Einführung eines neuen obligatorischen Tag. In diesem Fall besteht der einzige Weg zur Gewährleistung der Integrität darin, für jedes entsprechende Asset geeignete Tags zu erstellen. Da dies wahrscheinlich nicht automatisch durchgeführt werden kann, muss eventuelle Konsistenz akzeptiert werden, bis entsprechende Tags durch manuelle Intervention bereitgestellt werden.

Mit all diesen Ansätzen haben Sie nicht die Art von Integrität, die Sie mit einem RDMS erhalten würden. Die Verantwortung für das Erzwingen von aggregationsübergreifenden Invarianten wird an Befehlshandler, Ereignishandler und auf Aufrufcode delegiert. In vielen Fällen ist diese Konsistenz jedoch durchaus akzeptabel.

Werfen Sie einen Blick auf Effective Aggregate Design für mehr darüber.

+0

Danke für die Antwort. Ich denke, die mögliche Konsistenz ist die Antwort, wie Sie gesagt haben. Obwohl @ GiacomoTesio auch – MJM

+0

große Hilfe geleistet hat, würde ich auch anderen zeigen, dass der Instinkt, Bedingungen abzuleiten, allzu einfach ist. Wie wir festgestellt haben, sind die tatsächlichen Fakten, dass Assets aus AssteTypes erstellt werden, dann aber mehr oder weniger vom ursprünglichen Typ getrennt sind. Die Referenz kann zu jedem Zeitpunkt auf die ursprüngliche Spezifikation des Assets zurückgebracht werden und als Ergebnis in gewisser Weise aktualisiert werden, aber diese Aktion wird vom Aufrufer aufgerufen und nicht als Nebeneffekt des Speicherns des ursprünglichen Typs. Diese Erkenntnis hat enorm geholfen. – MJM

4

Kann eine Aggregationsinvariante eine Regel enthalten, die auf Informationen von anderen Quellen basiert?

Aggregate können immer die Informationen in ihren eigenen Zuständen und das Argument, dass ihre commands erhalten.

Jemand verwendet, um auf die Anwendungsdienste über Singletons, Service Locators und so weiter zugreifen, aber IMO, das ist ein Geruch von eng gekoppelten Anwendungen. Sie vergessen, dass die Argumente der Methoden effektive Injektoren der Abhängigkeit sind! :-)

In DDD kann eine Aggregationsinvariante eine Regel basierend auf Informationen in einem anderen Aggregat enthalten?

No.
sei denn, der zweite Aggregat über Befehle Argumente versehen ist, selbstverständlich.

WARNUNG! ! !

Ich habe ein Unternehmen namens Vermögen (Ausrüstung) ...
... (und a) zweites Aggregat genannt AssetType ...

Das letzte Mal, dass ich bewältigen musste eine ähnliche Struktur, es war ein Schmerz.

Wahrscheinlichkeiten sind, dass Sie die falschen Abstraktionen wählen.

habe ich meine Invariante falsch?

Wahrscheinlich ... Haben Sie nach dem Domänenexperten gefragt? Ist er spricht über "TagTypes"?

You should never abstract on your own.

Entitäten des Typs X halten einen Verweis auf eine Instanz von X-Type sind fast immer ein Geruch von Über-Abstraktion, die in der Hoffnung auf Wiederverwendung, macht das Modell starr und unflexibel für die Geschäftsentwicklung.

ANTWORT

Wenn (und nur dann) der Domain-Experte beschrieben tatsächlich das Modell in dieser Hinsicht ein möglicher Ansatz ist die folgende:

  1. Sie können eine AssetType Klasse erstellen mit Eine Factory-Methode, die einen IEnumerable<Tag> in einen TagSetand throws entweder MissingMandatoryTagException oder UnexpectedTagException verwandelt, wenn ein Teil des Tags fehlt oder unerwartet ist.
  2. in der Asset Klasse, ein Befehl RegisterTags würde eine AssetType und eine IEnumerable<Tag> akzeptieren, werfen die MissingMandatoryTagException und WrongAssetTypeException (beachten Sie, wie Ausnahmen wichtig sind Invarianten zu gewährleisten).

bearbeiten
so etwas, aber viel mehr dokumentiert:

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); 
    } 
} 
+1

Ihr Verdacht, dass das Problem in der Modellierung ist, ist richtig. Wir haben einige Zeit (viel tatsächlich) verbracht, um herauszufinden, was genau passiert. Ich denke, die "Eventual Consistency" -Lösung ist die beste. Wenn sie entscheiden, dass sie eine neue Information zu einem Typ auflisten möchten, liefern sie die Informationen nicht vor Ort an alle Assets. Also würde eine Methode "RebuildTags" auf Asset den AssetType lesen und dann bei Bedarf pro Asset ungültig machen (und vielleicht auch ein "DetectTagErrors"). Ihre Antwort ist ausgezeichnet und hat dies hervorgehoben, obwohl @eulerfx die tatsächliche Antwort hat. – MJM