2014-06-26 17 views
15

Sto cercando consigli su dove aggiungere le regole di convalida per le entità di dominio e le migliori pratiche per l'implementazione. Ho cercato e non ho trovato quello che stavo cercando, o l'ho perso.DDD Regole commerciali e convalida

Mi piacerebbe sapere qual è il modo consigliato per convalidare che le proprietà non siano nulle, in un certo intervallo, o lunghezza, ecc ... Ho visto diversi modi usando IsValid() e altre discussioni sull'imposizione in il costruttore quindi l'entità non è mai in uno stato non valido, o utilizza la pre-elaborazione e postelaborazione, e altri utilizza l'API di FluentValidation, in che modo gli invarianti influiscono su DRY e SRP.

Qualcuno può darmi un buon esempio di dove inserire questi tipi di controlli, quando si utilizza un servizio app, un contesto limitato, un servizio di dominio, una radice aggregata, una stratificazione di entità. Dove va questo, e qual è l'approccio migliore?

Grazie.

+0

Un esempio: https://github.com/szjani/predaddy-issuetracker-sample/blob/master/src/hu/szjani/domain/issue/Issue.php – inf3rno

+0

Imho non dovrebbero avere un impatto SRP. È possibile eseguire una convalida prima di impostare una proprietà dell'oggetto dominio, ad esempio nei comandi se si utilizza CQRS, quindi si è certi che sono validi ... È possibile annotare gli oggetti dominio e utilizzare tali dati per la convalida. – inf3rno

risposta

32

Quando si modella la propria entità di dominio, è meglio considerare le implicazioni nel mondo reale. Supponiamo che tu abbia a che fare con un'entità Employee.

I dipendenti hanno bisogno di un nome

Sappiamo che nel mondo reale un dipendente deve sempre avere un nome. È impossibile per un dipendente non avere un nome. In altre parole, non è possibile "costruire" un dipendente senza specificare il suo nome. Quindi, usa costruttori parametrizzati! Sappiamo anche che il nome di un dipendente non può cambiare, quindi impediamo che ciò accada anche creando un setter privato. L'utilizzo del sistema di tipo .NET per verificare il tuo dipendente è una forma di convalida molto forte.

public string Name { get; private set; } 

public Employee(string name) 
{ 
    Name = name; 
} 

Nomi validi sono alcune regole

Ora si comincia a diventare interessante. Un nome ha determinate regole. Prendiamo semplicemente il percorso semplicistico e assumiamo che un nome valido sia uno che non sia nullo o vuoto. Nell'esempio di codice sopra riportato, la seguente regola aziendale non è convalidata. A questo punto, possiamo ancora creare dipendenti non validi! Cerchiamo di evitare che ciò si verifichi mai, modificando il nostro setter:

public string Name 
{ 
    get 
    { 
     return name; 
    } 
    private set 
    { 
     if (String.IsNullOrWhiteSpace(value)) 
     { 
      throw new ArgumentOutOfRangeException("value", "Employee name cannot be an empty value"); 
     } 

     name = value; 
    } 
} 

Personalmente preferisco avere questa logica nel setter privato che nel costruttore. Il setter non è completamente invisibile. L'entità stessa può ancora cambiarla, e dobbiamo garantire la validità. Inoltre, genera sempre eccezioni!

Che dire dell'esposizione di qualche forma del metodo IsValid()?

Prendere l'entità Employee precedente. Dove e come funzionerebbe un metodo IsValid()?

Permetterebbe che venga creato un Dipendente non valido e quindi si aspetta che lo sviluppatore verifichi la sua validità con un controllo IsValid()? Questo è un design debole: prima che tu lo sappia, i dipendenti senza nome stanno andando in giro per il tuo sistema causando il caos.

Ma forse si desidera esporre la logica di convalida del nome?

Non vogliamo rilevare eccezioni per il flusso di controllo. Le eccezioni sono per un errore di sistema catastrofico.Inoltre, non vogliamo duplicare queste regole di convalida nella nostra base di codici. Quindi, forse esporre questa logica di convalida non è una cattiva idea (ma non è ancora la migliore!).

Che cosa si potrebbe fare è fornire un metodo statico IsValidName(string):

public static bool IsValidName(string name) 
{ 
    return (String.IsNullOrWhiteSpace(value)) 
} 

nostra struttura sarebbe ora cambiare un po ':

public string Name 
{ 
    get 
    { 
     return name; 
    } 
    private set 
    { 
     if (!Employee.IsValidName(value)) 
     { 
      throw new ArgumentOutOfRangeException("value", "Employee name cannot be an empty value"); 
     } 

     name = value; 
    } 
} 

Ma c'è qualcosa di sospetto su questo disegno ...

Ora stiamo iniziando a generare metodi di convalida per le singole proprietà della nostra entità. Se una proprietà ha tutti i tipi di regole e comportamenti collegati, forse questo è un segno che possiamo creare un oggetto valore per questo!

public PersonName : IEquatable<PersonName> 
{ 
    public string Name 
    { 
     get 
     { 
      return name; 
     } 
     private set 
     { 
      if (!PersonName.IsValid(value)) 
      { 
       throw new ArgumentOutOfRangeException("value", "Person name cannot be an empty value"); 
      } 

      name = value; 
     } 
    } 

    private PersonName(string name) 
    { 
     Name = name; 
    } 

    public static PersonName From(string name) 
    { 
     return new PersonName(name); 
    } 

    public static bool IsValid(string name) 
    { 
     return !String.IsNullOrWhiteSpace(value); 
    } 

    // Don't forget to override .Equals 
} 

Ora la nostra entità Employee può essere semplificata (ho escluso un controllo di riferimento null):

public Employee 
{ 
    public PersonName Name { get; private set; } 

    public Employee(PersonName name) 
    { 
     Name = name; 
    } 
} 

Il nostro codice cliente può ora essere simile a questa:

if(PersonName.IsValid(name)) 
{ 
    employee = new Employee(PersonName.From(name)); 
} 
else 
{ 
    // Send a validation message to the user or something 
} 

Così cosa abbiamo fatto qui?

Abbiamo assicurato che il nostro modello di dominio è sempre coerente. Estremamente importante. Non è possibile creare un'entità non valida. Inoltre, abbiamo utilizzato oggetti di valore per fornire ulteriore "ricchezza". PersonName ha dato al codice client più controllo e più potenza e ha anche semplificato Employee.

+3

+1 Questo è probabilmente il miglior esempio di come incapsulare la maggior parte delle mie esigenze di convalida che io abbia mai letto. –

+3

+1 è bello vedere una risposta da qualcuno che capisce veramente DDD, questa è una rarità su StackOverflow !! – MattDavey

+0

@AdrianThompsonPhillips se ti piacciono queste idee, dai anche un'occhiata a [questo video da NDC] (http://vimeo.com/97507575) – MattDavey