2011-09-05 18 views
17

Nonostante abbia studiato a lungo lo Domain Driven Design, ci sono ancora alcune nozioni di base che ho semplicemente capito.Problemi nel mettere la logica del mondo reale nel livello di dominio DDD

Sembra che ogni volta che cerco di progettare un ricco domain layer, ho ancora bisogno di un sacco di Domain Services o di una spessa Application Layer, e io alla fine con un gruppo di entità del dominio quasi anemici con alcuna logica vera in loro, a parte da "GetTotalAmount" e simili. Il problema chiave è che le entità non sono consapevoli di cose esterne, ed è una cattiva pratica iniettare qualsiasi cosa in entità.

Vi faccio alcuni esempi:

1. A utente si inscrive per un servizio. L'utente è persistente nel database, un file viene generato e salvato (necessario per l'account utente) e viene inviata un'e-mail di conferma.

L'esempio con l'e-mail di conferma è stato ampiamente discusso in altri thread, ma senza una vera conclusione. Alcuni suggeriscono di inserire la logica in un application service che ottiene uno EmailService e FileService iniettato dallo infrastructure layer. Ma allora avrei una logica di business al di fuori del dominio, giusto? Altri suggeriscono la creazione di un domain service che ottiene il infrastructure services iniettato - ma in quel caso avrei bisogno di avere le interfacce del infrastructure services all'interno del domain layer (IEmailService e IFileService) che non sembra troppo buono (perché il domain layer non può fare riferimento alla infrastructure layer) . Altri suggeriscono di implementare lo Udi Dahan's Domain Events e quindi di inviare EmailService e FileService a tali eventi. Ma sembra un'implementazione molto sciolta - e cosa succede se i servizi falliscono? Per favore fatemi sapere cosa ne pensate sia la soluzione giusta qui.

2. Una canzone viene acquistata da un negozio di musica digitale. Il carrello è svuotato. L'acquisto è persistente. Il servizio di pagamento è chiamato. Viene inviata un'email di conferma.

Ok, questo potrebbe essere correlato al primo esempio. La domanda qui è, chi è il responsabile dell'orchestrazione di questa transazione? Naturalmente potrei mettere tutto nel controller MVC con servizi iniettati. Ma se voglio il DDD reale, tutte le logiche di business dovrebbero essere nel dominio. Ma quale entità dovrebbe avere il metodo "Acquista"? Song.Purchase()? Order.Purchase()? OrderProcessor.Purchase() (servizio di dominio)? ShoppingCartService.Purchase() (servizio applicazione?)

Questo è un caso in cui ritengo sia molto difficile utilizzare la logica di business reale all'interno delle entità di dominio. Se non è buona pratica di iniettare niente nelle entità, come possono mai fare altre cose che controllare il proprio stato (e del suo aggregato)?

Spero che questi esempi sono abbastanza chiare per mostrare i problemi che ho a che fare.

+0

DDD suggerisce di rendere le entità 'File' e' Email' del dominio.L'infrastruttura è responsabile di generare effettivamente un file e di inviare effettivamente una e-mail quando le entità corrispondenti appaiono nel livello di dominio. – Lightman

risposta

8

Un utente si iscrive per un servizio.L'utente viene mantenuto nel database , un file viene generato e salvato (necessario per l'account utente), e un'email di conferma viene inviata.

Qui è possibile applicare Dependency Inversion Principle. Definire un'interfaccia dominio come questo:

void ICanSendConfirmationEmail(EmailAddress address, ...) 

o

void ICanNotifyUserOfSuccessfulRegistration(EmailAddress address, ...) 

interfaccia può essere utilizzato da altre classi di dominio. Implementa questa interfaccia nel livello infrastruttura, utilizzando le classi SMTP reali. Inietti questa implementazione all'avvio dell'applicazione. In questo modo hai dichiarato l'intenzione di business nel codice di dominio e la logica del tuo dominio non ha un riferimento diretto all'infrastruttura SMTP. La chiave qui è il nome dell'interfaccia, dovrebbe essere basata su Ubiquitous Language.

Una canzone viene acquistata da un negozio di musica digitale. Il carrello acquisti viene svuotato. L'acquisto è persistente. Il servizio di pagamento è chiamato. Viene inviata un'email di conferma. Ok, questo potrebbe essere correlato al primo esempio. La domanda qui è, chi è il responsabile dell'orchestrazione di questa transazione?

Utilizzare le migliori pratiche OOP per assegnare le responsabilità (GRASP e SOLID). Il collaudo e il refactoring dell'unità forniranno un feedback sul design. L'orchestrazione stessa può far parte del livello di applicazione sottile. Da DDD Layered Architecture:

Livello applicazione: Definisce i lavori il software dovrebbe fare e dirige gli oggetti di dominio espressivi per risolvere i problemi. Le attività a cui è assegnato questo livello sono significative per l'azienda o necessarie per l'interazione con i livelli dell'applicazione di altri sistemi.

Questo strato è mantenuto sottile. Non contiene regole di business o conoscenza, ma coordina compiti ed i delegati lavorano per collaborazioni di oggetti di dominio nello strato immediatamente inferiore. Non avere stato che riflette la situazione di business, ma può avere stato che riflette il progresso di un compito per l'utente o il programma.

+0

Grazie! Ottima idea con le interfacce di dominio. E dopo aver controllato più campioni DDD, sono convinto che non dovrei temere di inserire * alcune * business logic nel livello dell'applicazione - come la gestione di quale ordine chiamare gli oggetti del dominio. – lasseschou

+0

Ciao Dmitry, ho una domanda riguardante le interfacce di dominio. Consiglieresti di iniettare un'implementazione delle interfacce di dominio direttamente sulle entità di dominio? (è una buona pratica?) - o creeresti un servizio di dominio 'UserService' con un'implementazione iniettata, e poi chiamerai quel servizio dal livello dell'applicazione (' UserService.SendConfirmationEmail() ')? Non mi è ancora chiaro come implementare questa soluzione. – lasseschou

0

grande parte di te richieste sono legati a oggetto di design oriented e l'assegnazione di responsabilità, si può pensare di GRASP Patterns e This, è possibile beneficiare di object oriented libri di design, consigliamo il seguente

Applying UML and Patterns

11

La risposta di Dimitry indica alcune cose buone da cercare. Spesso/facilmente ti trovi nel tuo scenario, con uno spalamento dei dati da db fino a GUI attraverso diversi livelli.

Sono stato ispirato da semplici consigli di Jimmy Nilsson "oggetti di valore, oggetti di valore e altri oggetti di valore". Spesso le persone tendono a concentrarsi su Nouns e modellarle come entità. Naturalmente spesso hai problemi a trovare il comportamento DDD. I verbi sono più facili da associare al comportamento. Una buona cosa è far apparire questi verbi nel tuo dominio come oggetti Value.

Alcune linee guida che uso per la mia auto quando si cerca di sviluppare il dominio (devo dire che ci vuole tempo per costruire un dominio ricco, spesso diverse iterazioni di refactoring ...):

  • Minimizzare proprietà (get/set)
  • Usa valore di oggetti quanto più è possibile
  • Esporre il meno possibile. Rendi intuitivi i metodi di aggregazione dei domini.

Non dimenticare che il tuo dominio può essere ricco facendo la convalida. È solo il tuo dominio che sa come effettuare un acquisto e cosa è richiesto.

Il dominio dovrebbe essere responsabile della convalida quando le entità effettuano una transizione da uno stato a due stati (convalide del flusso di lavoro).

Vi darò alcuni esempi: Ecco un articolo che ho scritto sul mio blog per quanto riguarda il problema su di dominio anemico http://magnusbackeus.wordpress.com/2011/05/31/preventing-anemic-domain-model-where-is-my-model-behaviour/

posso anche consigliare davvero articolo del blog di Jimmy Bogard su convalide di entità e l'utilizzo di modello Validator insieme con metodi di estensione. Ti dà la libertà di convalidare le cose infrastrutturali senza sporcare il tuo dominio: http://lostechies.com/jimmybogard/2007/10/24/entity-validation-with-visitors-and-extension-methods/

Uso gli eventi di dominio di Udi con grande successo. Puoi anche renderli asincroni se ritieni che il tuo servizio possa fallire. Lo si avvolge anche in una transazione (utilizzando il framework NServiceBus).

Nel tuo primo esempio (solo brainstorming ora per convincere la nostra mente a pensare più a oggetti di valore).

  1. Il servizio di applicazione MusicService.AddSubscriber(User newUser) riceve una chiamata da un relatore/controllore/WCF con un nuovo utente. Il servizio ha già ricevuto IUserRepository e IMusicServiceRepository immesso in Ctor.
  2. Il servizio di musica "Spotify" viene caricato tramite IMusicServiceRepository
  3. metodo entità musicService.SignUp(MusicServiceSubscriber newSubsriber) prende un oggetto di valore MusicServiceSubscriber. Questo oggetto Value deve contenere Utente e altri oggetti obbligatori nel codice (gli oggetti valore sono immutabili). Qui puoi anche inserire logica/comportamento come handle subscriptionId's ecc.
  4. Quale metodo SignUp esegue anche, esso genera un evento di dominio NewSubscriberAddedToMusicService. Viene catturato da EventHandler HandleNewSubscriberAddedToMusicServiceEvent che ha ottenuto IFileService e IEmailService immesso nel suo codice. L'implementazione di questo gestore si trova nel livello del servizio di applicazione, MA l'evento è controllato dal dominio e MusicService.SignUp. Questo significa che il dominio ha il controllo. Eventhandler crea file e invia email.

È possibile mantenere l'utente tramite eventhandler OPPURE effettuare il metodo MusicService.AddSubscriber(...). Entrambi lo faranno attraverso IUserRepository ma è una questione di gusti e forse come rifletterà il dominio attuale.

Finalmente ... spero che tu riesca a cogliere qualcosa di quanto sopra ... comunque. La cosa più importante è iniziare ad aggiungere metodi "Verbi" alle entità e fare la collaborazione. Puoi anche avere oggetti nel tuo dominio che non sono persistenti, sono lì solo per mediare tra diverse entità di dominio e possono ospitare algoritmi ecc.

+0

Ciao - grazie mille per tutte le tue grandi idee. Peccato, posso solo contrassegnare una risposta, perché questo post mi guida nella giusta direzione, proprio come quella di Dmitry. Solo una domanda: se utilizzi eventi di dominio, magari anche in modo asincrono, come gestisci la situazione in cui non è possibile inviare la notifica via email? Sarebbe solo una gestione personalizzata delle eccezioni nel gestore di eventi? – lasseschou

+0

Se si utilizza NServiceBus come piattaforma, utilizza le transazioni MSDTC, quindi se il codice non funziona, il messaggio verrà ripristinato (credo). tuttavia Se tutto funziona e il tuo messaggio è in coda, allora il server SMTP è scaduto a causa di un errore del server di posta ... beh. Quindi devi catturare quel messaggio di errore e informare l'utente sull'errore della posta. Ma NserviceBus proverà a inviare questa email 5 (predefinito 5 volte). Quindi è possibile fare in modo che venga segnalato un errore in una coda di errore. È quindi possibile gestire tale messaggio di errore e informare l'utente sull'errore. –

+0

ok, grazie Magnus. Heja dalla Danimarca! – lasseschou

Problemi correlati