Domanda

Questa domanda è indipendente dal linguaggio ma io sono un tipo C # quindi uso il termine POCO per indicare un oggetto che preforma solo l'archiviazione dei dati, di solito utilizzando i campi getter e setter.

Ho appena rielaborato il mio modello di dominio in modo che diventi POCO superduper e sono rimasto con un paio di preoccupazioni su come garantire che i valori delle proprietà abbiano senso nel dominio.

Ad esempio, la Data di fine di un servizio non deve superare la Data di fine del contratto a cui è soggetto il servizio. Tuttavia, sembra una violazione di SOLID inserire il controllo nel setter Service.EndDate, per non parlare del fatto che man mano che il numero di convalide che devono essere fatte aumenta, le mie classi POCO diventeranno disordinate.

Ho alcune soluzioni (pubblicherò le risposte), ma hanno i loro svantaggi e mi chiedo quali sono alcuni approcci preferiti per risolvere questo dilemma?

È stato utile?

Soluzione

Penso che tu stia iniziando con una cattiva ipotesi, cioè che dovresti avere oggetti che non fanno altro che archiviare dati e non hanno metodi ma accessori. Il punto centrale di avere oggetti è incapsulare dati e comportamenti . Se hai una cosa che è fondamentalmente solo una struttura, quali comportamenti stai incapsulando?

Altri suggerimenti

Sento sempre l'argomento persone per un " Convalida " o " IsValid " metodo.

Personalmente penso che potrebbe funzionare, ma con la maggior parte dei progetti DDD di solito finisci con più convalide consentite a seconda dello stato specifico dell'oggetto.

Quindi preferisco " IsValidForNewContract " ;, " IsValidForTermination " o simili, perché credo che la maggior parte dei progetti finisca con più di questi validatori / stati per classe. Ciò significa anche che non ottengo alcuna interfaccia, ma posso scrivere validatori aggregati che leggi riflettono molto bene le condizioni commerciali che sto affermando.

Credo davvero che le soluzioni generiche in questo caso molto spesso focalizzino lontano da ciò che è importante - cosa sta facendo il codice - per un guadagno molto piccolo nell'eleganza tecnica (l'interfaccia, il delegato o qualsiasi altra cosa ). Votami per questo;)

Un mio collega ha avuto un'idea che ha funzionato abbastanza bene. Non abbiamo mai trovato un nome eccezionale, ma lo abbiamo chiamato Ispettore / Giudice.

L'ispettore guardava un oggetto e ti diceva tutte le regole che violava. Il giudice avrebbe deciso cosa fare al riguardo. Questa separazione ci permette di fare un paio di cose. Ci consente di mettere tutte le regole in un unico posto (ispettore) ma potremmo avere più giudici e scegliere il giudice in base al contesto.

Un esempio dell'uso di più giudici ruota attorno alla regola secondo cui un cliente deve avere un indirizzo. Questa era un'app standard a tre livelli. Nel livello dell'interfaccia utente, il giudice avrebbe prodotto qualcosa che l'interfaccia utente avrebbe potuto utilizzare per indicare i campi che dovevano essere compilati. Il giudice dell'interfaccia utente non ha generato eccezioni. Nel livello di servizio c'era un altro giudice. Se trovasse un cliente senza indirizzo durante il salvataggio genererebbe un'eccezione. A quel punto devi davvero impedire che le cose procedano.

Avevamo anche giudici più severi con il variare dello stato degli oggetti. Era una domanda di assicurazione e durante il processo di quotazione una polizza poteva essere salvata in uno stato incompleto. Ma una volta che la Politica era pronta per essere resa attiva, molte cose dovevano essere impostate. Quindi il Giudice per le citazioni dal lato del servizio non era severo come il Giudice di attivazione. Tuttavia, le regole utilizzate nell'Inspector erano sempre le stesse, quindi puoi ancora dire cosa non è completo anche se hai deciso di non fare nulla al riguardo.

Una soluzione è fare in modo che DataAccessObject di ciascun oggetto prenda un elenco di validatori. Quando viene chiamato Save, viene eseguito un controllo su ciascun validatore:

public class ServiceEndDateValidator : IValidator<Service> {
  public void Check(Service s) {
    if(s.EndDate > s.Contract.EndDate)
      throw new InvalidOperationException();
  }
}

public class ServiceDao : IDao<Service> {
  IValidator<Service> _validators;
  public ServiceDao(IEnumerable<IValidator<Service>> validators) {_validators = validators;}
  public void Save(Service s) {
    foreach(var v in _validators)
      v.Check(service);
    // Go on to save
  }
}

Il vantaggio è che il SoC è molto chiaro, lo svantaggio è che non otteniamo il controllo finché non viene chiamato Save ().

In passato di solito ho delegato la convalida a un servizio a sé stante, come un servizio di convalida. Questo, in linea di principio, continua ad ascoltare la filosofia di DDD.

Internamente questo conterrebbe una raccolta di validatori e una serie molto semplice di metodi pubblici come Validate () che potrebbe restituire una raccolta di oggetti errore.

Molto semplicemente, qualcosa del genere in C #

public class ValidationService<T>
{
  private IList<IValidator> _validators;

  public IList<Error> Validate(T objectToValidate)
  {
    foreach(IValidator validator in _validators)
    {
      yield return validator.Validate(objectToValidate);
    }
  }
}

I validatori possono essere aggiunti all'interno di un costruttore predefinito o iniettati tramite un'altra classe come ValidationServiceFactory.

Penso che probabilmente sarebbe il posto migliore per la logica, in realtà, ma sono solo io. Potresti avere un qualche tipo di metodo IsValid che controlla anche tutte le condizioni e restituisce vero / falso, forse un qualche tipo di raccolta ErrorMessages ma questo è un argomento incerto poiché i messaggi di errore non fanno realmente parte del modello di dominio. Sono un po 'di parte perché ho fatto un po' di lavoro con RoR ed è essenzialmente quello che fanno i suoi modelli.

Un'altra possibilità è quella di implementare ciascuna delle mie classi

public interface Validatable<T> {
  public event Action<T> RequiresValidation;
}

E fai in modo che ciascun setter per ogni classe rilasci l'evento prima dell'impostazione (forse potrei raggiungerlo tramite gli attributi).

Il vantaggio è il controllo di validazione in tempo reale. Ma codice più disordinato ed è poco chiaro chi dovrebbe fare l'allegato.

Ecco un'altra possibilità. La convalida viene effettuata tramite un proxy o un decoratore sull'oggetto Dominio:

public class ServiceValidationProxy : Service {
  public override DateTime EndDate {
    get {return EndDate;}
    set {
      if(value > Contract.EndDate)
        throw new InvalidOperationexception();
      base.EndDate = value;
    }
  }
}

Vantaggio: validazione istantanea. Può essere facilmente configurato tramite un IoC.

Svantaggio: se un proxy, le proprietà convalidate devono essere virtuali, se un decoratore tutti i modelli di dominio devono essere basati su interfaccia. Le classi di validazione finiranno per essere un po 'pesanti: i proxy devono ereditare la classe e i decoratori devono implementare tutti i metodi. La denominazione e l'organizzazione potrebbero diventare confuse.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top