2012-12-07 11 views
21

Sfondo: Per la mia formazione di chiarezza/sé, sto cercando di realizzare una semplice applicazione Order Entry usando TDD + DDD. Il mio obiettivo principale è mantenere l'architettura pulita separando le preoccupazioni.Dove implementare Automapper in DDD + architettura a strati

Ho quattro strati (per ora) ...

  1. Persistenza/DAL con una classe CustomerRepository in grado di eseguire GetById, Salva, le operazioni sul "root aggregata", un cliente e le sue Ordini correlati e OrderItems. Per consentire "l'iniezione di dipendenza da uomo povero"

  2. Livello dominio/BLL contenente classi "entità aziendali" che eseguono operazioni a grana fine per aiutare a creare nuovi ordini, applicare tasse, sconti, logica di spedizione in base alla dimensione dell'ordine e posizione del cliente.

  3. Application Facade (servizi di app/orchestrazione) contenente classi complesse e di grana grossa per orchestrare le "entità di business" e ridurre le chatter con la presentazione (e potenzialmente un livello di WebServices).

  4. livello di presentazione

Inoltre, voglio passare POCO DTO tra gli strati chiave ... in particolare tra gli strati di persistenza => dominio e ApplicationFacade => livelli di presentazione. Quindi, ho CustomerDto, OrderDto, OrderItemDto con le relazioni appropriate definite in un pacchetto condiviso.

Voglio iniettare un'implementazione dell'ICustomerRepository nella classe "business entity" del cliente utilizzando Constructor Injection, quindi chiamare Customer.Save() sulla "entità aziendale" per avviare il processo di creazione/aggiornamento, in definitiva chiamando il metodo Save sul CustomerRepository. Dopo tutto, il Cliente è la "radice aggregata" e ha tutte le informazioni necessarie per salvare ... è anche il "custode" del CustomerRepository immesso.

Problema: Qui è dove ho colpito un intoppo. Voglio mantenere il Dominio/BLL Layer il più puro possibile ed evitare di accoppiarlo a qualsiasi framework e API di terze parti, ma il metodo Customer.Save() deve tradurre il Cliente "radice aggregata" e tutti i suoi Ordini e OrderItem nelle loro versioni DTO per il trasporto al layer di Persistenza inserito CustomerRepository ... e questo è un lavoro per Automapper.

Il problema è ... Se non metto Automapper nel livello Dominio/BLL, non sono proprio sicuro che sia dove dovrebbe andare.

Non è corretto inserirlo in ApplicationFacade anche se il lavoro è un'orchestrazione.

Non è assolutamente giusto inserirlo nel livello Dominio/BLL perché voglio mantenerlo intatto.

Pertanto, mi sento come se mi sia sfuggito qualcosa ... che mi sto avvicinando a questo con un fondamentale fraintendimento su come le parti di lavoro dovrebbero riunirsi per completare questo compito. Eventuali suggerimenti? (Si prega di essere gentile, sono nuovo di tutto questo e nuovo di SO. Fammi sapere se ho bisogno di mostrare un codice di quello che ho finora.)

risposta

41

Per rispondere alla tua domanda specifica parlerò più in generale sulla tua architetturaL'architettura che hai ideato per il tuo progetto è leggermente diversa da un'architettura tipica utilizzata con DDD. Mentre il modo in cui hai suddiviso le responsabilità è tipico, in DDD, le classi di dominio non sono responsabili della loro persistenza. In effetti un mantra di DDD è persistence ignorance. Ciò significa fondamentalmente che le classi di dominio sono ignoranti alla persistenza - non hanno un metodo Save. Invece, i servizi applicativi (strato di facciata dell'applicazione nella propria architettura) si coordinano con gli archivi per ricostituire e mantenere le entità di dominio.

Successivamente, quando si implementano i repository, di solito non è necessario un DTO esplicito tra la tecnologia di persistenza sottostante e le classi di dominio. Con ORM come NHibernate ed EF, i mapping tra classi di dominio e tabelle relazionali sono espressi con classi di mapping o configurazioni basate su XML. Di conseguenza, le tue classi di dominio vengono mappate implicitamente, senza necessità di DTO. Alcuni casi richiedono un DTO, nel qual caso il mapping tra DTO e classe di dominio dovrebbe essere incapsulato dall'implementazione del repository. Questo è un luogo in cui è possibile richiamare AutoMapper.

Infine, è prassi comune comunicare tra il livello di presentazione e il livello applicazione/dominio con DTO. Per determinare esattamente dove dovrebbe avvenire la mappatura, devi scavare più a fondo nell'architettura che meglio si adatta al tuo progetto. Le più moderne architetture DDD sono basate sullo Hexagonal architecture. In un'architettura esagonale, il tuo dominio è al centro e tutti gli altri "livelli" adattano il dominio a tecnologie specifiche. Ad esempio, un repository può essere visto come un adapter tra il dominio e una tecnologia di database specifica. ASP.NET WebAPI può essere visto come un adattatore tra il dominio e HTTP/REST. Allo stesso modo, il livello di presentazione può essere visto come un adattatore tra il dominio e uno specifico. I DTO possono manifestarsi all'interno di ciascuno di questi adattatori ed è responsabilità dell'adattatore mappare da e verso questi DTO.

Un tipico esempio potrebbe essere utilizzare i servizi di applicazione per stabilire una facciata sul dominio. Nessun DTO ancora in gioco. Successivamente, si crea un livello di servizio (aprire il servizio host in DDD) con ASP.NET WebAPI, un adattatore. È possibile creare DTO specifici di ASP.NET WebAPI e questo adattatore può essere mappato su e da questi DTO. Pertanto, se si utilizza AutoMapper, dovrebbe essere richiamato in questo livello. Questo può essere fatto in modo esplicito o in un modo aspect-oriented con filtri di azione. Lo stesso vale per il livello di presentazione. Il livello di presentazione può essere accoppiato direttamente ai servizi delle applicazioni o a un servizio host aperto di ASP.NET WebAPI. In entrambi i casi, è responsabilità del livello di presentazione mappare tra le classi di dominio (dal servizio dell'applicazione o DTO da WebAPI) e le proprie primitive come ViewModel.

+1

Wow, grazie! Ottima risposta Sono completamente d'accordo con i primi due paragrafi: le distinzioni che hai individuato sono esatte. Gli ultimi due paragrafi mi serviranno più tempo per digerire, leggere alcuni dei riferimenti che hai fatto (in particolare l'architettura esagonale e il servizio host aperto) e vedere come questo impatta sul mio piccolo esperimento. Grazie mille per il tempo impiegato per rispondere. –

+2

Followup riguardante l'ultima frase del terzo paragrafo ... la mia motivazione per il ritorno di DTO dal livello applicationfacade/services - al contrario di restituire istanze di "entità aziendali" POCO al livello di presentazione - era di nascondere la grana più fine metodi definiti su ciascuna delle entità aziendali nel livello dominio/BLL. Voglio che quei metodi a grana fine vengano consumati dal livello applicationfacade/services NOT del livello di presentazione. Quindi, alla luce di ciò, c'è qualcosa che dovrei fare? Ho visto alcune interfacce di definizione per le loro entità di business. –

+2

Non definirei le interfacce per gli oggetti dominio perché DTO e oggetti dominio non sono sempre one-one. Puoi semplicemente creare DTO per passare tra i servizi applicativi e il livello di presentazione e mapparli con AutoMapper all'interno del servizio dell'applicazione. Di nuovo, questi DTO non corrispondono necessariamente direttamente a un oggetto dominio. Possono rappresentare argomenti per un metodo di caso d'uso del servizio applicativo specifico o valori di ritorno. Per un caso d'uso che crea una nuova entità, il DTO può assomigliare molto all'oggetto dominio corrispondente. – eulerfx