Modello di dominio, convalida e invio di errori al modello

5

Guardando al DDD e qualcosa che ho notato è che la logica di business dovrebbe essere nel modello, altrimenti hai solo sacchetti di proprietà. Detto questo, come gestisci i pezzi di convalida che richiedono un viaggio nel database?

Ad esempio, hai un oggetto dominio che rappresenta le categorie per un motore di blogging.

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Dal punto di vista dello sviluppatore, è possibile creare una categoria vuota, ma ciò non è valido nel mondo degli affari. A Category richiede un nome per essere una categoria valida, quindi finiamo con:

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }

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

Ora, Category non può essere creato senza un nome. Ma non possiamo nemmeno avere nomi di Category duplicati, il che causerebbe solo confusione e ridondanza. Non voglio che l'entità lo gestisca da sé perché questo finirebbe per aumentare di un Active Record Pattern e non voglio che il modello sia "auto-consapevole / persistente" per così dire.

Quindi, a questo punto, dove dovrebbe avvenire questa convalida . È importante che se l'utente digita un duplicato Category , informeremo l'utente di questo. Dovrebbe esserci un livello "servizio" che prende un modello UI e lo trasforma nel modello di dominio e tenta di convalidarlo, riportando gli errori all'interfaccia utente o dovrebbe esserci qualcosa di più vicino al livello dati che gestisce questo?

Qualcuno, per favore, fammi sapere perché mi piace molto dove sta andando DDD, non riesco a fare tutte le connessioni per arrivarci da solo.

    
posta Justin 08.10.2014 - 23:31
fonte

6 risposte

4

Creerei un servizio di dominio responsabile della convalida dell'unicità della categoria. Nel mio dominio di soluzione definisce l'interfaccia del servizio, la parte infrastrutturale lo implementa utilizzando un database.

Dai anche un'occhiata al link .

    
risposta data 09.10.2014 - 23:50
fonte
3

business logic should be in the model, otherwise you just have property bags

Che cos'è logica aziendale ?
La logica aziendale è che il tuo oggetto conosce lo stato e potrebbe rispondere a domande come order.IsDistributed o order.IsQualityAssuranceDone o simili. Non è una logica aziendale, se si pone la domanda questo è già presente nel database . Questa è una responsabilità diversa: non appartiene all'ordine stesso, poiché non è uno stato dell'ordine , è lo stato del database . Pertanto dovresti chiedere al database la risposta.

From a developer's perspective, you could create an empty category, but this isn't valid in the business world

Per mantenere uno stato pulito, è necessario utilizzare proprietà (in C #) / getter and setter , dove si implementa la logica di convalida, per mantenere lo stato dell'oggetto pulito e ordinato.

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }

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

Questo design ha un grande difetto : non rispecchia esattamente il tuo requisito di avere un nome , poiché il costruttore potrebbe essere chiamato con null , che non è in ogni senso un nome valido . D'altra parte, è possibile utilizzare il modello di builder ed eseguire la costruzione tramite un'interfaccia builder.

Now, a Category can't be created without a name. But, we can't have duplicate Category names either, that would just cause confusing and redundancy.

Questi sono due problemi diversi . Uno appartiene allo stato dell'oggetto l'altro allo stato del database.

Should there be a "service" layer that takes a UI model and turns it into the domain model and attempts to validate it, pushing errors back to the UI, or should there be something closer to the data layer that handles this?

Spetta a te decidere se utilizzare un livello di servizio. A volte è annegare piccoli cuccioli per fare l'indecisione. Dipende da quanta logica c'è.

    
risposta data 10.10.2014 - 17:14
fonte
2

D: Come creiamo gli oggetti? A: Usando le Fabbriche di Dominio!

Poiché la creazione di una categoria contiene una logica complessa (dobbiamo convalidare l'unicità), utilizzeremo una factory di domini per creare l'oggetto per noi.

All'interno di questo stabilimento userò un servizio UniqueCategoryNameValidator che chiama nel data-store (un DB nel tuo caso) e controlla se la categoria è univoca. Alcune persone potrebbero riferirsi a questo approccio usando il modello delle specifiche come segue:

UniqueCategoryNameSpecification.isSatisfiedBy(category); 

L'utilizzo di una specifica rende esplicito il controllo dell'unicità nel tuo dominio. Le specifiche utilizzerebbero un servizio di infrastruttura per interrogare il database.

    
risposta data 10.10.2014 - 16:28
fonte
1

Quello che faccio è che costruisco i miei soggetti (subordinati, oggetti all'interno di un dominio) in modo tale che non possano mai essere istanziati da qualcuno diverso dal < strong> dominio stesso. (L'oggetto di dominio main o centrale .) Ciò è in linea con le migliori pratiche: mai usa new operatore per creare un'istanza di oggetti che non appartengono a te (oggetti di altri pacchetti / spazi dei nomi / assiemi) invocano sempre le fabbriche per farlo. Il dominio si trova in una posizione di utilizzo di new per istanziare i propri soggetti, perché appartengono a questo.

L'istanziazione degli oggetti solo dal dominio può essere applicata dichiarando i loro costruttori come package-private, (assembly-internal,) o, meglio ancora, dichiarando le classi oggetto stesse come package-private, (assembly-internal,) e averli implementare interfacce pubbliche, (interfacce esposte dal dominio,) in modo che il mondo esterno non possa nemmeno pensare di istanziarle.

Quindi, poiché i soggetti possono essere istanziati solo dal dominio, è molto facile fare in modo che il dominio esegua la necessaria convalida durante la loro creazione, come suggerito da un'altra risposta, in modo da garantire che i soggetti siano validi sin dall'inizio.

Tuttavia, questo non risolve completamente il problema , perché in seguito potresti rinominare un oggetto, (memorizza un nome diverso in it), nel qual caso è necessario evitare nuovamente di dargli un nome che è già stato preso da un argomento diverso. Chiaramente, l'accesso al database è sempre necessario, al fine di fornire un servizio completo.

Quindi, la soluzione che ho trovato per questo è che ogni soggetto conosce il suo dominio .

Il dominio viene sempre passato come primo parametro al costruttore di ogni argomento. Ogni singola istanziazione di un soggetto dal dominio è simile alla seguente: new Subject( this, ... ) . In un certo senso, il ruolo svolto dal dominio nella programmazione basata sul dominio è parallelo al ruolo svolto dall'oggetto nella programmazione orientata agli oggetti: proprio come ogni metodo accetta come primo parametro nascosto il puntatore this , che punta all'oggetto, così ogni soggetto accetta come primo parametro costruttore il puntatore domain , che punta al dominio.

Quindi, ogni soggetto è perfettamente in grado di eseguire qualsiasi convalida è necessaria, invocando il dominio per fare la sua validazione.

Se non vuoi che i tuoi soggetti siano legati al dominio, allora i tuoi soggetti potrebbero essere considerati come aventi uno stato extra, indicando se sono in qualche momento parti di un dominio o meno. (persistente o no, equivalente allo stato "allegato / distaccato" di Hibernate). Quindi, un oggetto può iniziare la sua vita come non parte del dominio, ( non persistito , distaccato in Hibernate parlance,) nel qual caso non può eseguire alcuna convalida, e più tardi potrebbe diventare parte del dominio, ( persistito , allegato in Hibernate parlance,) a a quale punto riceve un riferimento al dominio e può fare qualsiasi verifica è necessaria. In seguito potresti addirittura rimuoverlo dal dominio, ( separarlo ,) a quel punto si annulla presumibilmente il puntatore del dominio e l'oggetto non sarebbe più in grado di eseguire la convalida.

    
risposta data 28.09.2015 - 17:29
fonte
1

we can't have duplicate Category names either, that would just cause confusing and redundancy.

Supponendo che tu abbia un invariante di business che non richiede nomi di categorie duplicati, allora la responsabilità di far rispettare quell'invariant appartiene a qualche aggregato. Poiché l'applicazione della regola richiede l'accesso a tutti i nomi di categoria attualmente in uso, lo stesso aggregato deve includere, come parte del suo stato, quella raccolta di nomi.

Questa è una costruzione aggregata di base: il controllo della coerenza e lo stato vanno insieme.

where should this validation take place?

Nel comando reserveName () dell'aggregato.

Should there be a "service" layer that takes a UI model and turns it into the domain model and attempts to validate it?

No, qui non dovrebbe esserci nulla di insolito. Il cliente desidera utilizzare un nuovo nome, quindi invia una rappresentazione del comando ReserveName all'applicazione. L'applicazione reidrata il comando, individua il gestore di comandi appropriato e invia il comando al gestore. Il gestore carica l'aggregato e invoca il metodo reserveName () utilizzando gli argomenti forniti. Questo segue esattamente lo stesso schema di ogni altro comando che invii al modello di dominio.

Se l'aggregato scopre che il nome della categoria è già in uso, genera un'eccezione di dominio e la tua applicazione lo segnala al client; di nuovo, esattamente come ogni altro comando indirizzato al modello di dominio.

Quale aggregato è "l'aggregato"? Mi fa battere: devi cercare nella lingua onnipresente per scoprire l'aggregato che rafforza questo invariante. Ad esempio, potrebbe esserci un aggregato che ha questa responsabilità e nient'altro. In un esempio di giocattolo come questo, potresti fingere che CategoryNameReservationBook sia qualcosa che ha senso per il business. O forse NameReservationBook, con un'istanza NRB utilizzata per i nomi delle categorie, e un'altra istanza NRB utilizzata per i titoli dei post dei blog, o qualcosa di simile.

Quando ne parli con i tuoi esperti di dominio, potresti scoprire che "nessuna categoria duplicata" non è davvero un limite difficile. Nelle , è importante sentire la differenza tra "che non deve mai accadere "e" che non dovrebbe accadere "; quest'ultima frase implica che accade occasionalmente, e se è così probabile che ci sia già un processo per quella contingenza.

Potresti anche trovare, nella discussione, che questo non è affatto un vincolo di business; in quale cast lo si estrae completamente dal modello di dominio - se è qualcosa di reale, forse l'handle dell'applicazione è solo, o forse c'è qualche altro modello di dominio (forse in un altro contesto limitato) che ne è responsabile.

Cercare di supportare questo vincolo con servizi e simili in realtà non funziona; il servizio in esecuzione in questa transazione non può vedere cosa viene modificato in altre transazioni, quindi ottieni una corsa di dati. Hai lo stesso problema nel tentativo di utilizzare una specifica: i dati utilizzati per inizializzare la specifica sono obsoleti e un'altra transazione può essere eseguita qui.

I modelli di dominio con condizioni di competizione sono comunque sospettosi; se due utenti che lavorano su attività separate si bloccano a vicenda, allora qualcosa non va. Udi Dahan è andato così lontano da scrivere che le condizioni di gara non esistono - che nel mondo degli affari, il conflitto non è impedito, così come viene rilevato e mitigato.

Quindi controlla, e ricontrolla, e controlla tre volte che forse il vincolo di unicità non è tutto questo, per quanto riguarda gli esperti di dominio. Ma se è davvero una regola aziendale che non ci sono duplicati, segue, incondizionatamente, che esiste un responsabile aggregato per tenerne traccia.

A ulteriore considerazione, un problema di unicità come questo potrebbe essere ambito ; il blog del programmatore ha una categoria denominata , e il blog di salute ha una categoria , ma non sono la stessa cosa. Questo potrebbe essere supportato dando a ciascun blog il proprio libro di prenotazione, ma potrebbe anche essere un suggerimento che la Categoria sia semplicemente un'entità, subordinata a qualche altro aggregato.

    
risposta data 29.01.2016 - 09:34
fonte
0

Perché non crei un servizio che contiene una raccolta di tutte le categorie? Quindi puoi fare validazione molto facilmente. Un esempio:

class BlogCategoryService 
{
   private List<Category> _categories = new List<Category>();

   public void AddCategory(string categoryName) 
   {
       if (_categories.Any(c => c.Name == categoryName)) 
           throw new DuplicateCategoryException(categoryName);

       var newId = ...

       _categories.Add(new Category(newId, categoryName));
   }
   ...
}

Effettua la convalida del nome nella tua categoria di classe:

public class Category
{
    public int Id { get; private set; }
    public string Name { get; private set; }

    public Category(int id, string name)
    {
        ValidateName(name);

        Id = id;
        Name = name;
    }

    private void ValidateName(string name)
    {
       if (string.IsNullOrWhitespace(categoryName))
           throw new ArgumentException('Category name may not be null or whitespace.');
    }

}

Se vuoi cambiare il nome in una categoria, puoi dipendere dal tuo servizio:

public class Category
{
    ...

    public void ChangeName(BlogCategoryService service, string newName)
    {
        if (service == null)
            throw new ArgumentNullException('service');

        ValidateName(newName);

        if (!service.IsUniqueCategoryName(newName))
            throw new DuplicateCategoryException(newName);

        Name = newName;
    }
}

public class BlogCategoryService 
{

    ...
    public bool IsUniqueCategoryName(string name)
    {
        return !_categories.Any(c => c.Name == name);
    }
}

Aggiungi anche un vincolo univoco sulla colonna del nome della tabella delle categorie del blog per evitare che qualcuno apporti modifiche al di fuori del tuo BlogCategoryService.

    
risposta data 20.10.2014 - 00:21
fonte

Leggi altre domande sui tag