it-swarm.com.de

Validierung in einem domänengetriebenen Design

Wie gehen Sie mit der Validierung komplexer Aggregate in einem domänengetriebenen Design um? Konsolidieren Sie Ihre Geschäftsregeln/Validierungslogik?

Ich verstehe Argumentvalidierung. Und ich verstehe die Eigenschaftsüberprüfung, die an die Modelle selbst angehängt werden kann, und überprüfe, ob eine E-Mail-Adresse oder Postleitzahl gültig ist oder ob ein Vorname eine Mindest- und Höchstlänge hat.

Wie sieht es aber mit der komplexen Validierung aus, die mehrere Modelle umfasst? Wo platzieren Sie diese Regeln und Methoden normalerweise in Ihrer Architektur? Und welche Muster verwenden Sie, um sie umzusetzen?

57
Todd Smith

Ich mag Jimmy Bogards Lösung für dieses Problem. In seinem Blog hat er einen Beitrag mit dem Titel "Entitätsvalidierung mit Besuchern und Erweiterungsmethoden" , in dem er einen sehr eleganten Ansatz für die Entitätsvalidierung präsentiert, der die Implementierung einer separaten Klasse zum Speichern von Validierungscode vorschlägt.

public interface IValidator<T>
{
    bool IsValid(T entity);
    IEnumerable<string> BrokenRules(T entity);
}

public class OrderPersistenceValidator : IValidator<Order>
{
    public bool IsValid(Order entity)
    {
        return BrokenRules(entity).Count() == 0;
    }

    public IEnumerable<string> BrokenRules(Order entity)
    {
        if (entity.Id < 0)
            yield return "Id cannot be less than 0.";

        if (string.IsNullOrEmpty(entity.Customer))
            yield return "Must include a customer.";

        yield break;
    }
}
37
David Negron

Anstatt sich auf IsValid(xx)-Anrufe in Ihrer gesamten Anwendung zu verlassen, sollten Sie einige Ratschläge von Greg Young in Betracht ziehen:

Lassen Sie Ihre Entitäten niemals in einen ungültigen Zustand gelangen.

Das bedeutet im Wesentlichen, dass Sie nicht mehr nur über reine Datencontainer, sondern über Objekte mit Verhalten denken.

Betrachten Sie das Beispiel der Adresse einer Person:

 person.Address = "123 my street";
 person.City = "Houston";
 person.State = "TX";
 person.Zip = 12345;

Zwischen diesen Aufrufen ist Ihre Entität ungültig (weil Sie über Eigenschaften verfügen, die nicht miteinander übereinstimmen. Betrachten Sie nun Folgendes:

person.ChangeAddress(.......); 

alle Aufrufe, die sich auf das Verhalten beim Ändern einer Adresse beziehen, sind jetzt eine atomare Einheit. Ihre Entität ist hier niemals ungültig.

Wenn Sie sich eher der Modellierung von Verhalten als dem Status zuwenden, können Sie ein Modell erreichen, das keine ungültigen Entitäten zulässt.

Eine gute Diskussion dazu finden Sie in diesem Infoq-Interview: http://www.infoq.com/interviews/greg-young-ddd

55
Ben Scheirman

Normalerweise verwende ich eine Spezifikationsklasse, Sie stellt eine Methode bereit (dies ist C #, aber Sie können sie in jede Sprache übersetzen):

bool IsVerifiedBy(TEntity candidate)

Diese Methode führt eine vollständige Prüfung des Kandidaten und seiner Beziehungen durch. Sie können Argumente in der Spezifikationsklasse verwenden, um ihn zu parametrisieren, z. B. eine Prüfstufe ...

Sie können auch eine Methode hinzufügen, um zu erfahren, warum der Kandidat die Spezifikation nicht verifiziert hat:

IEnumerable<string> BrokenRules(TEntity canditate) 

Sie können sich einfach entscheiden, die erste Methode wie folgt zu implementieren:

bool IsVerifiedBy(TEntity candidate)
{
  return BrokenRules(candidate).IsEmpty();
}

Für gebrochene Regeln schreibe ich normalerweise einen Iterator:

IEnumerable<string> BrokenRules(TEntity candidate)
{
  if (someComplexCondition)
      yield return "Message describing cleary what is wrong...";
  if (someOtherCondition) 
      yield return
   string.Format("The amount should not be {0} when the state is {1}",
        amount, state);
}

Für die Lokalisierung sollten Sie Ressourcen verwenden und warum nicht eine Kultur an die BrokenRules-Methode übergeben. Ich platziere diese Klassen im Modellnamensraum mit Namen, die deren Verwendung nahe legen.

6

Die Validierung mehrerer Modelle sollte Ihre Aggregatwurzel durchlaufen. Wenn Sie die Validierung über mehrere Stammgruppen hinweg durchführen müssen, liegt wahrscheinlich ein Konstruktionsfehler vor. 

Bei der Validierung von Aggregaten werde ich eine Antwortschnittstelle zurückgeben, in der mir mitgeteilt wird, ob die Validierung erfolgreich war/fehlgeschlagen ist, und Meldungen darüber, warum sie fehlgeschlagen ist. 

Sie können alle Untermodelle im Aggregatstamm validieren, damit sie konsistent bleiben. 

// Command Response class to return from public methods that change your model
public interface ICommandResponse
{
    CommandResult Result { get; }
    IEnumerable<string> Messages { get; }
}

// The result options
public enum CommandResult
{
    Success = 0,
    Fail = 1
}

// My default implementation
public class CommandResponse : ICommandResponse
{
    public CommandResponse(CommandResult result)
    {
        Result = result;
    }

    public CommandResponse(CommandResult result, params string[] messages) : this(result)
    {
        Messages = messages;
    }

    public CommandResponse(CommandResult result, IEnumerable<string> messages) : this(result)
    {
        Messages = messages;
    }

    public CommandResult Result { get; private set; }

    public IEnumerable<string> Messages { get; private set; }
}

// usage
public class SomeAggregateRoot
{
    public string SomeProperty { get; private set; }


    public ICommandResponse ChangeSomeProperty(string newProperty)
    {
        if(newProperty == null)
        {
            return new CommandResponse(CommandResult.Fail, "Some property cannot be changed to null");
        }

        SomeProperty = newProperty;

        return new CommandResponse(CommandResult.Success);
    }
}
0
Todd Skelton