Rich Domain Models: come si inserisce esattamente il comportamento?


84

Nel dibattito sui modelli di dominio Rich vs. Anemic, Internet è piena di consigli filosofici ma a corto di esempi autorevoli. L'obiettivo di questa domanda è trovare linee guida definitive ed esempi concreti di modelli di progettazione basati su dominio adeguati. (Idealmente in C #.)

Per un esempio reale, questa implementazione di DDD sembra essere sbagliata:

I modelli di dominio WorkItem di seguito non sono altro che sacche di proprietà, utilizzate da Entity Framework per un database in codice. Per Fowler, è anemico .

Il livello WorkItemService è apparentemente una percezione errata comune dei servizi di dominio; contiene tutta la logica di comportamento / business per WorkItem. Per Yemelyanov e altri, è procedurale . (pag.6)

Quindi, se il seguito è sbagliato, come posso farlo bene?
Il comportamento, ovvero AddStatusUpdate o Checkout , dovrebbe appartenere alla classe WorkItem corretta?
Quali dipendenze dovrebbe avere il modello WorkItem?

inserisci qui la descrizione dell'immagine

public class WorkItemService : IWorkItemService {
    private IUnitOfWorkFactory _unitOfWorkFactory;

    //using Unity for dependency injection
    public WorkItemService(IUnitOfWorkFactory unitOfWorkFactory) {
        _unitOfWorkFactory = unitOfWorkFactory;
    }

    public void AddStatusUpdate(int workItemId, int statusId) {

        using (var unitOfWork = _unitOfWorkFactory.GetUnitOfWork<IWorkItemUnitOfWork>()) {
            var workItemRepo = unitOfWork.WorkItemRepository;
            var workItemStatusRepo = unitOfWork.WorkItemStatusRepository;

            var workItem = workItemRepo.Read(wi => wi.Id == workItemId).FirstOrDefault();
            if (workItem == null)
                throw new ArgumentException(string.Format(@"The provided WorkItem Id '{0}' is not recognized", workItemId), "workItemId");

            var status = workItemStatusRepo.Read(s => s.Id == statusId).FirstOrDefault();
            if (status == null)
                throw new ArgumentException(string.Format(@"The provided Status Id '{0}' is not recognized", statusId), "statusId");

            workItem.StatusHistory.Add(status);

            workItemRepo.Update(workItem);
            unitOfWork.Save();
        }
    }
}

(Questo esempio è stato semplificato per essere più leggibile. Il codice è sicuramente ancora goffo, perché è un tentativo confuso, ma il comportamento del dominio è stato: aggiorna lo stato aggiungendo il nuovo stato alla cronologia dell'archivio. Alla fine sono d'accordo con le altre risposte, questo potrebbe essere gestito da CRUD.)

Aggiornare

@AlexeyZimarev ha dato la risposta migliore, un video perfetto sull'argomento in C # di Jimmy Bogard, ma apparentemente è stato spostato in un commento qui sotto perché non ha fornito abbastanza informazioni oltre il link. Ho una bozza dei miei appunti che riassume il video nella mia risposta di seguito. Non esitate a commentare la risposta con eventuali correzioni. Il video dura un'ora ma vale davvero la pena guardarlo.

Aggiornamento - 2 anni dopo

Penso che sia un segno della nascente maturità di DDD che anche dopo averlo studiato per 2 anni, non posso ancora promettere di conoscere il "modo giusto" di farlo. Il linguaggio diffuso, le radici aggregate e il suo approccio alla progettazione orientata al comportamento sono i preziosi contributi di DDD al settore. L'ignoranza di persistenza e l'approvvigionamento di eventi causano confusione, e penso che una filosofia del genere la trattiene da un'adozione più ampia. Ma se dovessi ripetere questo codice, con quello che ho imparato, penso che sarebbe simile a questo:

inserisci qui la descrizione dell'immagine

Sono ancora felice di ricevere risposte a questo post (molto attivo) che fornisca qualsiasi codice di best practice per un modello di dominio valido.


6
Tutte le teorie filosofiche cadono a terra quando le dici "I don't want to duplicate all my entities into DTOs simply because I don't need it and it violates DRY, and I also don't want my client application to take a dependency on EntityFramework.dll". "Entità" nel gergo Entity Framework non sono le stesse di "Entità" come in "Modello di dominio"
Federico Berasategui,

Sono d'accordo con la duplicazione delle mie entità di dominio in un DTO, utilizzando uno strumento automatizzato come Automapper, se è quello che serve. Non sono sicuro di come dovrebbe essere alla fine della giornata.
RJB,

16
Ti consiglio di guardare la sessione NDC 2012 di Jimmy Bogard "Creare modelli di dominio malvagi" su Vimeo . Spiega quale ricco dominio dovrebbe essere e come implementarli nella vita reale avendo un comportamento nelle tue entità. Gli esempi sono molto pratici e tutti in C #.
Alexey Zimarev,

Grazie, sono a metà del video e questo è perfetto finora. Sapevo che se ciò fosse sbagliato, doveva esserci una risposta "giusta" là fuori da qualche parte ....
RJB

2
Chiedo anche amore per Java: /
uylmz,

Risposte:


59

La risposta più utile è stata data da Alexey Zimarev e ha ottenuto almeno 7 voti prima che un moderatore lo spostasse in un commento sotto la mia domanda originale ....

La sua risposta:

Ti consiglierei di guardare la sessione NDC 2012 di Jimmy Bogard "Creare modelli di dominio malvagi" su Vimeo. Spiega quale ricco dominio dovrebbe essere e come implementarli nella vita reale avendo un comportamento nelle tue entità. Gli esempi sono molto pratici e tutti in C #.

http://vimeo.com/43598193

Ho preso alcune note per riassumere il video a beneficio sia del mio team sia per fornire un dettaglio un po 'più immediato in questo post. (Il video dura un'ora, ma vale davvero la pena ogni minuto se hai tempo. Jimmy Bogard merita molto credito per la sua spiegazione.)

  • "Per la maggior parte delle applicazioni ... non sappiamo che saranno complesse quando iniziamo. Diventano così."
    • La complessità cresce naturalmente con l'aggiunta di codice e requisiti. Le applicazioni possono iniziare in modo molto semplice, come CRUD, ma il comportamento / le regole possono essere integrati.
    • "La cosa bella è che non dobbiamo iniziare in modo complesso. Possiamo iniziare con il modello di dominio anemico, che è solo sacche di proprietà e con solo tecniche di refactoring standard possiamo spostarci verso un vero modello di dominio".
  • Modelli di dominio = oggetti business. Comportamento del dominio = regole aziendali.
  • Il comportamento è spesso nascosto in un'applicazione: può essere in PageLoad, Button1_Click o spesso in classi di supporto come 'FooManager' o 'FooService'.
  • Le regole aziendali separate dagli oggetti di dominio "richiedono di ricordare" tali regole.
    • Nel mio esempio personale sopra, una regola aziendale è WorkItem.StatusHistory.Add (). Non stiamo solo cambiando lo stato, lo stiamo archiviando per l'auditing.
  • Comportamenti di dominio "eliminano i bug in un'applicazione molto più facilmente rispetto alla semplice scrittura di numerosi test". I test richiedono che tu sappia scrivere quei test. I comportamenti del dominio ti offrono i percorsi giusti da testare .
  • I servizi di dominio sono "classi di supporto per coordinare le attività tra diverse entità del modello di dominio".
    • Servizi di dominio! = Comportamento del dominio. Le entità hanno un comportamento, i servizi di dominio sono solo intermediari tra le entità.
  • Gli oggetti di dominio non dovrebbero essere in possesso dell'infrastruttura di cui hanno bisogno (ad es. IOfferCalculatorService). Il servizio di infrastruttura deve essere passato al modello di dominio che lo utilizza.
  • I modelli di dominio dovrebbero offrire di dirti cosa possono fare e dovrebbero essere in grado di fare solo quelle cose.
  • Le proprietà dei modelli di dominio devono essere protette con setter privati, in modo che solo il modello possa impostare le proprie proprietà, attraverso i propri comportamenti . Altrimenti è "promiscuo".
  • Gli oggetti del modello di dominio anemico, che sono solo sacchi di proprietà per un ORM, sono solo "un rivestimento sottile - una versione fortemente tipizzata sul database".
    • "Per quanto sia facile inserire una riga del database in un oggetto, questo è quello che abbiamo."
    • 'La maggior parte dei modelli di oggetti persistenti sono proprio questo. Ciò che differenzia un modello di dominio anemico rispetto a un'applicazione che non ha realmente un comportamento, è se un oggetto ha regole di business, ma tali regole non si trovano in un modello di dominio. '
  • "Per molte applicazioni, non è necessario creare alcun tipo di livello logico per le applicazioni aziendali reali, è solo qualcosa che può comunicare con il database e forse un modo semplice per rappresentare i dati presenti."
    • Quindi, in altre parole, se tutto ciò che stai facendo è CRUD senza oggetti business speciali o regole di comportamento, non hai bisogno di DDD.

Sentiti libero di commentare con qualsiasi altro punto che ritieni debba essere incluso o se ritieni che una qualsiasi di queste note non sia corretta. Ho cercato di citare direttamente o parafrasare il più possibile.


Ottimo video soprattutto per vedere come funziona il refactoring in uno strumento. Molto riguarda l'incapsulamento corretto degli oggetti di dominio (per assicurarsi che siano coerenti). Fa un ottimo lavoro nel dire le regole aziendali su offerte, membri, ecc. Fa menzione della parola invariante un paio di volte (che è la modellazione di domini basata su contratto). Vorrei che il codice .net comunicasse meglio ciò che è una regola aziendale formale, poiché questi cambiano e devi mantenerli.
Fuhrmanator,

6

Non è possibile rispondere alla tua domanda, perché il tuo esempio è sbagliato. In particolare, perché non esiste alcun comportamento. Almeno non nell'area del tuo dominio. L'esempio di AddStatusUpdatemetodo non è una logica di dominio, ma una logica che utilizza quel dominio. Questo tipo di logica ha senso essere all'interno di un qualche tipo di servizio, che gestisce le richieste esterne.

Ad esempio, se c'era la necessità che un determinato oggetto di lavoro potesse avere solo stati specifici o che potesse avere solo N stati, allora quella è la logica di dominio e dovrebbe essere parte di uno WorkItemo StatusHistorydi un metodo.

Il motivo della tua confusione è perché stai cercando di applicare una linea guida al codice che non ne ha bisogno. I modelli di dominio sono rilevanti solo se si dispone di molte logiche di dominio complesse. Per esempio. logica che funziona sulle entità stesse e deriva da requisiti. Se il codice riguarda la manipolazione di entità da dati esterni, probabilmente questa non è una logica di dominio. Ma nel momento in cui ottieni molte ifs in base ai dati e alle entità con cui stai lavorando, questa è la logica del dominio.

Uno dei problemi della vera modellazione del dominio è che si tratta di gestire requisiti complessi. E come tale il suo vero potere e i suoi benefici non possono essere mostrati su un semplice codice. Hai bisogno di dozzine di entità con tonnellate di requisiti intorno per vedere veramente i benefici. Ancora una volta, il tuo esempio è troppo semplice perché il modello di dominio possa davvero brillare.

Infine, una cosa OT che vorrei menzionare è che un vero modello di dominio con un vero design OOP sarebbe davvero difficile da perseguire usando Entity Framework. Mentre gli ORM sono stati progettati con la mappatura della struttura OOP reale su quelli relazionali, ci sono ancora molti problemi e il modello relazionale spesso si insinua nel modello OOP. Anche con nHibernate, che considero molto più potente di EF, questo può essere un problema.


Punti buoni. Dove dovrebbe quindi appartenere il metodo AddStatusUpdate, in Data o in un altro progetto in Infrastruttura? Qual è un esempio di qualsiasi comportamento che potrebbe teoricamente appartenere a WorkItem? Qualsiasi psuedo-codice o modello sarebbe molto apprezzato. Il mio esempio è stato in realtà semplificato per essere più leggibile. Esistono altre entità e, ad esempio, AddStatusUpdate ha un comportamento aggiuntivo: in realtà richiede un nome di categoria di stato e, se tale categoria non esiste, viene creata la categoria.
RJB

@RJB Come ho detto, AddStatusUpdate è il codice che utilizza il dominio. Quindi un qualche tipo di servizio web o applicazione che utilizza le classi di dominio. E come ho detto, non puoi aspettarti alcun tipo di mockup o pseudocodice, perché dovresti rendere l'intero progetto di complessità abbastanza grande da mostrare il vero vantaggio del modello di dominio OOP.
Euforico

5

La tua supposizione che incapsulare la tua logica aziendale associata a WorkItem in un "servizio scadente" è un modello intrinseco intrinseco che direi non è necessariamente.

Indipendentemente dai tuoi pensieri sul modello di dominio anemico, i modelli e le pratiche standard tipici di un'applicazione Line of Business .NET incoraggiano un approccio transazionale a più livelli composto da vari componenti. Incoraggiano la separazione della logica aziendale dal modello di dominio in modo specifico per facilitare la comunicazione di un modello di dominio comune attraverso altri componenti .NET nonché componenti su stack tecnologici diversi o su livelli fisici.

Un esempio di questo potrebbe essere un servizio web SOAP basato su .NET che comunica con un'applicazione client Silverlight che ha una DLL contenente tipi di dati semplici. Questo progetto di entità di dominio potrebbe essere integrato in un assembly .NET o Silverlight, in cui i componenti Silverlight interessati che dispongono di questa DLL non saranno esposti a comportamenti di oggetti che potrebbero dipendere da componenti disponibili solo per il servizio.

Indipendentemente dalla tua posizione in questo dibattito, questo è lo schema adottato e accettato presentato da Microsoft e secondo la mia opinione professionale non è un approccio sbagliato ma quindi un modello a oggetti che definisce il proprio comportamento non è necessariamente un anti-modello. Se vai avanti con questo design, è meglio realizzare e comprendere alcuni dei limiti e dei punti critici che potresti incontrare se devi integrarti con altri componenti che devono vedere il tuo modello di dominio. In quel caso particolare, forse potresti volere che un traduttore converta il tuo modello di dominio di stile orientato agli oggetti in semplici oggetti dati che non espongono determinati metodi di comportamento.


1
1) Come si può separare la logica aziendale dal modello di dominio? È il dominio in cui vive questa logica aziendale; le entità in quel dominio stanno eseguendo il comportamento associato a quella logica aziendale. Il mondo reale non ha servizi, né esistono nei capi di esperti di dominio. 2) Qualsiasi componente che vuole integrarsi con voi ha bisogno di costruire il proprio modello di dominio, perché i suoi bisogni sarà diverso e sarà avere una visione diversa sul modello di dominio. È un phallacy di vecchia data che è possibile creare un modello di dominio che può essere condiviso.
Stefan Billiet,

1
@StefanBilliet Questi sono aspetti positivi dell'errore di un modello di dominio universale, ma è possibile in componenti e interazioni più semplici come ho già fatto prima. La mia opinione è che la logica di traduzione tra i modelli di dominio può rendere un sacco di codice noioso e caloroso e se può essere evitato in modo sicuro, può essere una buona scelta di progettazione.
maple_shaft

1
Ad essere sincero, penso che l'unica buona scelta di design sia un modello su cui un esperto di affari possa ragionare. Stai costruendo un modello di dominio, che un'azienda può utilizzare per risolvere alcuni problemi all'interno di quel dominio. Dividere il comportamento delle entità di dominio in servizi rende più difficile per tutti i soggetti coinvolti, poiché è necessario mappare costantemente ciò che dicono gli esperti di dominio al codice di servizio che non assomiglia quasi alla conversazione corrente. Nella mia esperienza, perdi molto più tempo con quello, che digitando boilerplate. Questo non vuol dire che non ci sono modi per aggirare il codice del corso della caldaia.
Stefan Billiet,

@StefanBilliet In un mondo perfetto sono d'accordo con te dove un esperto di affari ha il tempo di sedersi con gli sviluppatori. La realtà dell'industria del software è che l'esperto aziendale non ha tempo o interesse a essere coinvolto a questo livello o peggio, ma gli sviluppatori dovrebbero solo capirlo con una vaga guida.
maple_shaft

Vero, ma questo non è un motivo per accettare quella realtà. Continuare in tale ricerca significa perdere tempo (e possibilmente reputazione) degli sviluppatori e denaro del cliente. Il processo che ho descritto è una relazione che deve essere costruita nel tempo; richiede molto sforzo, ma produce risultati molto migliori. C'è una ragione per cui "Ubiquitous Language" è spesso considerato l'aspetto più importante di DDD.
Stefan Billiet,

5

Mi rendo conto che questa domanda è piuttosto vecchia, quindi questa risposta è per i posteri. Voglio rispondere con un esempio concreto anziché uno basato sulla teoria.

Incapsula la "modifica dello stato dell'elemento di lavoro" sulla WorkItemclasse in questo modo:

public SomeStatusUpdateType Status { get; private set; }

public void ChangeStatus(SomeStatusUpdateType status)
{
    // Maybe we designed this badly at first ;-)
    Status = status;       
}

Ora la tua WorkItemclasse è responsabile del mantenimento in uno stato legale. L'implementazione è piuttosto debole, tuttavia. Il proprietario del prodotto desidera una cronologia di tutti gli aggiornamenti di stato apportati a WorkItem.

Lo cambiamo in qualcosa del genere:

private ICollection<SomeStatusUpdateType> StatusUpdates { get; private set; }
public SomeStatusUpdateType Status => StatusUpdates.OrderByDescending(s => s.CreatedOn).FirstOrDefault();

public void ChangeStatus(SomeStatusUpdateType status)
{
    // Better...
    StatusUpdates.Add(status);       
}

L'implementazione è cambiata drasticamente ma il chiamante del ChangeStatusmetodo non è a conoscenza dei dettagli di implementazione sottostanti e non ha motivo di cambiare se stesso.

Questo è un esempio di un'entità modello di dominio avanzato, IMHO.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.