I servizi devono sempre restituire DTO o possono anche restituire modelli di dominio?


175

Sto (ri) progettando un'applicazione su larga scala, usiamo l'architettura multi-layer basata su DDD.

Abbiamo MVC con livello dati (implementazione di repository), livello di dominio (definizione del modello di dominio e interfacce - repository, servizi, unità di lavoro), livello di servizio (implementazione di servizi). Finora, utilizziamo modelli di dominio (principalmente entità) su tutti i livelli e utilizziamo DTO solo come modelli di vista (nel controller, i modelli di dominio di restituzione del servizio e il controller creano il modello di vista, che viene passato alla vista).

Ho letto innumerevoli articoli sull'uso, non sull'uso, sulla mappatura e sul superamento dei DTO. Comprendo che non esiste una risposta definitiva, ma non sono sicuro che sia corretto o non restituisca i modelli di dominio dai servizi ai controller. Se restituisco il modello di dominio, non viene mai passato alla vista, poiché il controller crea sempre un modello di vista specifico per la vista - in questo caso, sembra legittimo. D'altra parte, non sembra giusto quando il modello di dominio lascia il livello aziendale (livello di servizio). A volte il servizio deve restituire un oggetto dati non definito nel dominio e quindi è necessario aggiungere un nuovo oggetto al dominio non mappato o creare un oggetto POCO (è brutto, poiché alcuni servizi restituiscono modelli di dominio, alcuni restituire effettivamente DTO).

La domanda è: se utilizziamo rigorosamente i modelli di visualizzazione, è possibile restituire i modelli di dominio ai controller o dovremmo sempre utilizzare DTO per la comunicazione con il livello di servizio? In tal caso, è corretto regolare i modelli di dominio in base ai servizi necessari? (Francamente non la penso così, dal momento che i servizi dovrebbero consumare quale dominio ha.) Se dovessimo attenerci rigorosamente ai DTO, dovrebbero essere definiti nel livello di servizio? (Penso di sì.) A volte è chiaro che dovremmo usare DTO (ad esempio, quando il servizio esegue molta logica aziendale e crea nuovi oggetti), a volte è chiaro che dovremmo usare solo modelli di dominio (ad esempio, quando il servizio di appartenenza restituisce un utente anemico ( s) - sembra che non avrebbe molto senso creare DTO uguale al modello di dominio - ma preferisco coerenza e buone pratiche.

Articolo Dominio vs DTO vs ViewModel - Come e quando usarli? (e anche alcuni altri articoli) è molto simile al mio problema, ma non risponde a questa / e domanda / e. Articolo Dovrei implementare DTO nel modello di archivio con EF? è anche simile, ma non si occupa di DDD.

Dichiarazione di non responsabilità: non intendo utilizzare alcun modello di progettazione solo perché esiste ed è elegante, d'altra parte, vorrei utilizzare buoni modelli e pratiche di progettazione anche perché aiuta a progettare l'applicazione nel suo insieme, aiuta con la separazione delle preoccupazioni, anche il fatto di usare un modello particolare non è "necessario", almeno al momento.

Come sempre, grazie.


28
Per quei ragazzi che votano da vicino - per favore, ti dispiacerebbe spiegare perché vuoi chiudere questa domanda come opinione?
Robert Goldwein,

20
@Aron "Code Review è un sito di domande e risposte per la condivisione di codice dai progetti su cui stai lavorando per la revisione tra pari." - la mia domanda non riguarda affatto il codice, quindi sarebbe fuori tema lì; SO: "Concentrati sulle domande su un problema reale che hai affrontato. Includi dettagli su ciò che hai provato ed esattamente cosa stai cercando di fare." - Ho un problema specifico da esperto, che ho cercato di risolvere. Potresti per favore essere più specifico cosa c'è di sbagliato in questa domanda, dal momento che molte domande qui riguardano l'architettura e tali domande sono apparentemente ok, quindi posso evitare ulteriori equivoci?
Robert Goldwein,

7
Grazie per aver posto questa domanda. Mi hai fatto un favore e mi hai reso la vita molto più semplice e felice, grazie.
Loa,

9
@RobertGoldwein, non importa la SO Close Mafia, la tua domanda è legittima.
hyankov,

3
Grazie mille per aver posto questa domanda
Salman,

Risposte:


177

non sembra giusto quando il modello di dominio lascia il livello aziendale (livello di servizio)

Ti fa sentire come se stessi tirando fuori le viscere, giusto? Secondo Martin Fowler: il livello di servizio definisce i limiti dell'applicazione, incapsula il dominio. In altre parole protegge il dominio.

A volte il servizio deve restituire un oggetto dati non definito nel dominio

Potete fornire un esempio di questo oggetto dati?

Se dovessimo attenerci rigorosamente ai DTO, dovrebbero essere definiti nel livello di servizio?

Sì, perché la risposta fa parte del tuo livello di servizio. Se è definito "altrove", il livello di servizio deve fare riferimento a tale "altrove", aggiungendo un nuovo livello alla lasagna.

va bene restituire i modelli di dominio ai controller o dovremmo sempre utilizzare DTO per la comunicazione con il livello di servizio?

Un DTO è un oggetto risposta / richiesta, ha senso se lo usi per la comunicazione. Se usi i modelli di dominio nel tuo livello di presentazione (MVC-Controller / View, WebForms, ConsoleApp), il livello di presentazione è strettamente accoppiato al tuo dominio, qualsiasi modifica nel dominio ti richiede di cambiare i tuoi controller.

sembra che non avrebbe molto senso creare DTO uguale al modello di dominio)

Questo è uno degli svantaggi di DTO per i nuovi occhi. In questo momento, stai pensando alla duplicazione del codice , ma man mano che il tuo progetto si espande, avrebbe molto più senso, specialmente in un ambiente di team in cui team diversi sono assegnati a layer diversi.

DTO potrebbe aggiungere ulteriore complessità alla tua applicazione, ma lo sono anche i tuoi livelli. DTO è una funzione costosa del tuo sistema, non sono gratuiti.

Perché usare un DTO

Questo articolo offre sia vantaggi che svantaggi dell'utilizzo di un DTO, http://guntherpopp.blogspot.com/2010/09/to-dto-or-not-to-dto.html

Riepilogo come segue:

Quando usare

  • Per grandi progetti.
  • La durata del progetto è di 10 anni e oltre.
  • Applicazione strategica e mission-critical.
  • Squadre di grandi dimensioni (più di 5)
  • Gli sviluppatori sono distribuiti geograficamente.
  • Il dominio e la presentazione sono diversi.
  • Ridurre gli scambi di dati generali (lo scopo originale di DTO)

Quando non usare

  • Progetto di piccole e medie dimensioni (massimo 5 membri)
  • La durata del progetto è di circa 2 anni.
  • Nessun team separato per GUI, back-end, ecc.

Argomenti contro DTO

Argomenti con DTO

  • Senza DTO, la presentazione e il dominio sono strettamente associati. (Questo è ok per piccoli progetti.)
  • Stabilità interfaccia / API
  • Può fornire l'ottimizzazione per il livello di presentazione restituendo un DTO contenente solo quegli attributi che sono assolutamente richiesti. Usando linq-projection , non devi tirare un'intera entità.
  • Per ridurre i costi di sviluppo, utilizzare strumenti di generazione del codice

3
Ciao, grazie per la tua risposta, è davvero un buon riassunto per, e anche grazie per i collegamenti. La mia frase "A volte il servizio deve restituire un oggetto dati non definito nel dominio" è stata scelta male, significa che il servizio combina diversi DO da un repository (ad esempio, attributi) e produce un POCO come composizione di questi attributi (basato sulla logica aziendale). Ancora una volta, grazie per la bella risposta.
Robert Goldwein,

1
Un'importante considerazione delle prestazioni è il modo in cui tali modelli di dominio o DTO vengono restituiti dal tuo servizio. Con EF, se realizzi una query per restituire una raccolta concreta di modelli di dominio (con un .ToArray () o ToList (), ad esempio) selezioni tutte le colonne per popolare gli oggetti realizzati. Se si proietta invece il DTO nella query, EF è abbastanza intelligente da selezionare solo le colonne necessarie per popolare il DTO, che in alcuni casi può essere significativamente meno dati da trasferire.
sbuffa il

10
Puoi mappare i tuoi oggetti "a mano". Lo so, roba noiosa, ma richiede 2-3 minuti per modello e c'è sempre la possibilità di portare molti problemi quando si usa molta riflessione (AutoMapper ecc.)
Razvan Dumitru

1
grazie per aver risposto in modo così semplice e con tali contenuti. Mi hai fatto un favore e mi hai reso la vita molto più semplice e felice, grazie.
Loa,

1
Abbiamo annullato un progetto da 10 milioni perché doveva rallentare ... Perché è stato lento? Trasferimento dell'oggetto DTO dappertutto usando la rielezione. Stai attento. Automapper usa anche la riflessione.
RayLoveless,

11

Sembra che l'applicazione sia abbastanza grande e complessa poiché hai deciso di seguire l'approccio DDD. Non restituire le tue entità poco o cosiddette entità di dominio e oggetti valore nel tuo livello di servizio. Se vuoi farlo, elimina il tuo livello di servizio perché non ti serve più! Gli oggetti Visualizza modello o Trasferimento dati devono vivere nel livello Servizio perché devono essere associati ai membri del modello di dominio e viceversa. Allora perché hai bisogno di avere DTO? In un'applicazione complessa con molti scenari è necessario separare le preoccupazioni relative al dominio e le visualizzazioni di presentazione, un modello di dominio potrebbe essere suddiviso in più DTO e anche diversi modelli di dominio potrebbero essere compressi in un DTO. Quindi è meglio creare il tuo DTO in un'architettura a strati anche se sarebbe lo stesso del tuo modello.

Dovremmo usare sempre DTO per la comunicazione con il livello di servizio? Sì, devi restituire DTO dal tuo livello di servizio mentre parli con il tuo repository nel livello di servizio con i membri del modello di dominio e mapparli a DTO e tornare al controller MVC e viceversa.

È corretto regolare i modelli di dominio in base ai servizi necessari? Un servizio parla solo con repository e metodi di dominio e servizi di dominio, dovresti risolvere il business nel tuo dominio in base alle tue esigenze e non è compito del servizio dire al dominio ciò che è necessario.

Se dovessimo attenerci rigorosamente ai DTO, dovrebbero essere definiti nel livello di servizio? Sì, prova ad avere DTO o ViewModel appena in servizio perché dovrebbero essere mappati ai membri del dominio nel livello di servizio e non è una buona idea posizionare DTO nei controller della tua applicazione (prova a utilizzare il modello Richiedi risposta nel tuo livello di servizio), applausi !


1
scusa per quello! puoi vederlo qui ehsanghanbari.com/blog/Post/7/…
Ehsan,

10

Nella mia esperienza dovresti fare ciò che è pratico. "Il miglior design è il design più semplice che funziona" - Einstein. Con questo è la mente ...

se utilizziamo rigorosamente i modelli di visualizzazione, è possibile restituire i modelli di dominio ai controller o dovremmo sempre utilizzare DTO per la comunicazione con il livello di servizio?

Assolutamente va bene! Se si dispone di entità di dominio, DTO e modelli di visualizzazione, comprese le tabelle del database, tutti i campi nell'applicazione vengono ripetuti in 4 posizioni. Ho lavorato su grandi progetti in cui Entità di dominio e Modelli vista funzionavano perfettamente. L'unica aspettativa è se l'applicazione è distribuita e il livello di servizio risiede su un altro server, nel qual caso i DTO devono inviare attraverso il cavo per motivi di serializzazione.

In tal caso, è corretto regolare i modelli di dominio in base ai servizi necessari? (Francamente non la penso così, dal momento che i servizi dovrebbero consumare quale dominio ha.)

In generale, sono d'accordo e direi di no perché il modello di dominio è in genere un riflesso della logica aziendale e di solito non viene modellato dal consumatore di tale logica.

Se dovessimo attenerci rigorosamente ai DTO, dovrebbero essere definiti nel livello di servizio? (Credo di si.)

Se decidessi di usarli sarei d'accordo e direi di sì il livello di servizio è il posto perfetto in quanto restituisce i DTO alla fine della giornata.

In bocca al lupo!


8

Sono in ritardo a questa festa, ma questa è una domanda così comune e importante che mi sono sentito in dovere di rispondere.

Per "servizi" intendi lo "strato applicativo" descritto da Evan nel libro blu ? Darò per scontato che fai, in questo caso la risposta è che essi dovrebbero non tornare DTOs. Suggerisco di leggere il capitolo 4 del libro blu, intitolato "Isolating the Domain".

In quel capitolo, Evans dice quanto segue sui livelli:

Partiziona un programma complesso in livelli. Sviluppa un disegno all'interno di ogni strato che sia coerente e che dipenda solo dagli strati sottostanti.

C'è una buona ragione per questo. Se si utilizza il concetto di ordine parziale come misura della complessità del software, avere un livello dipende da un livello sopra aumenta la complessità, diminuendo la manutenibilità.

Applicando questo alla tua domanda, i DTO sono in realtà un adattatore che riguarda il livello Interfaccia utente / Presentazione. Ricorda che la comunicazione remota / tra processi è esattamente lo scopo di un DTO (vale la pena notare che in quel post Fowler sostiene anche che i DTO fanno parte di un livello di servizio, sebbene non stia necessariamente parlando in linguaggio DDD).

Se il livello dell'applicazione dipende da tali DTO, dipende da un livello sopra se stesso e la complessità aumenta. Posso garantire che ciò aumenterà la difficoltà di mantenere il tuo software.

Ad esempio, cosa succede se il sistema si interfaccia con molti altri sistemi o tipi di client, ognuno dei quali richiede il proprio DTO? Come fai a sapere quale DTO deve restituire un metodo del tuo servizio applicativo? Come risolveresti questo problema anche se la tua lingua preferita non consente di sovraccaricare un metodo (metodo di servizio, in questo caso) basato sul tipo restituito? E anche se trovi un modo, perché violi il tuo livello applicazione per supportare una preoccupazione del livello presentazione?

In termini pratici, questo è un passo lungo una strada che finirà in un'architettura di spaghetti. Ho visto questo tipo di devoluzione e i suoi risultati nella mia esperienza.

Dove attualmente lavoro, i servizi nel nostro livello applicazione restituiscono oggetti dominio. Non consideriamo questo un problema poiché il livello Interface (cioè UI / Presentation) dipende dal livello Domain, che è sotto di esso. Inoltre, questa dipendenza è ridotta a un tipo di dipendenza "solo riferimento" perché:

a) il livello interfaccia è in grado di accedere a questi oggetti dominio solo come valori di ritorno di sola lettura ottenuti dalle chiamate al livello applicazione

b) i metodi sui servizi nel Livello applicazione accettano come input solo input "grezzi" (valori di dati) o parametri oggetto (per ridurre il conteggio dei parametri ove necessario) definiti in quel livello. In particolare, i servizi applicativi non accettano mai oggetti di dominio come input.

Il livello interfaccia utilizza le tecniche di mappatura definite all'interno del livello interfaccia stesso per mappare dagli oggetti Dominio ai DTO. Ancora una volta, questo mantiene i DTO concentrati sull'essere adattatori che sono controllati dal livello di interfaccia.


1
Domanda veloce. Attualmente sto ruotando su cosa tornare dal mio livello di applicazione. Restituire entità di dominio dal livello applicazione sembra sbagliato. Voglio davvero far trapelare il dominio "al di fuori"? Quindi stavo contemplando DTO dal livello dell'applicazione. Ma questo aggiunge un altro modello. Nella tua risposta hai dichiarato di restituire i modelli di dominio come "valori di ritorno di sola lettura". Come si fa a farlo? Cioè, come li fai leggere solo?
Michael Andrews,

Penso che adotterò la tua posizione. I servizi applicativi restituiscono modelli di dominio. I livelli dell'adattatore della porta (REST, presentazione, ecc.), Quindi li traducono nel proprio modello (visualizza il modello o le rappresentazioni). L'aggiunta di un modello DTO tra l'applicazione e gli adattatori delle porte sembra eccessiva. Il ritorno dei modelli di dominio aderisce comunque al DIP e la logica del dominio rimane all'interno del contesto limitato (non necessariamente all'interno del limite dell'applicazione. Ma questo sembra un ottimo compromesso).
Michael Andrews,

@MichaelAndrews, felice di sapere che la mia risposta mi ha aiutato. Ri: la tua domanda sugli oggetti restituiti è di sola lettura, gli oggetti stessi non sono veramente di sola lettura (cioè immutabili). Quello che voglio dire è che non succede nella pratica (almeno nella mia esperienza). Per aggiornare un oggetto dominio, il livello interfaccia dovrebbe o a) fare riferimento al repository dell'oggetto dominio oppure b) richiamare nel livello applicazione per aggiornare l'oggetto appena ricevuto. Ognuna di queste sono violazioni così evidenti della buona pratica DDD che le trovo auto-applicate. Sentiti libero di votare la risposta se ti interessa.
BitMask777,

Questa risposta è molto intuitiva per me per diversi motivi. Innanzitutto, possiamo riutilizzare lo stesso livello applicazione per diverse UI (API, controller) e ognuno può trasformare il modello come meglio ritiene. In secondo luogo, se dovessimo trasformare il modello da DTO in App. Livello, ciò significherebbe che DTO è definito in App. Layer, che a sua volta significa che DTO è ora parte del nostro Contesto limitato (non necessariamente Dominio!) - questo sembra semplicemente sbagliato.
Robotron,

1
Stavo per farti una domanda di follow-up e poi ti ho visto già risposto: "i servizi applicativi non accettano mai oggetti di dominio come input". Vorrei fare +1 di nuovo se potessi.
Robotron,

5

In ritardo alla festa, ma sto affrontando lo stesso identico tipo di architettura e sono incline a "solo DTO dal servizio". Questo principalmente perché ho deciso di utilizzare solo oggetti / aggregati di dominio per mantenere la validità all'interno dell'oggetto, quindi solo durante l'aggiornamento, la creazione o l'eliminazione. Quando eseguiamo una query per i dati, utilizziamo EF solo come repository e mappiamo il risultato su DTO. Questo ci rende liberi di ottimizzare le query di lettura e non adattarle agli oggetti business, spesso utilizzando le funzioni del database in quanto sono veloci.

Ogni metodo di servizio definisce il proprio contratto ed è quindi più facile da mantenere nel tempo. Io spero.


1
Dopo anni siamo giunti alla stessa conclusione, esattamente per i motivi che hai citato qui.
Robert Goldwein,

@RobertGoldwein È fantastico! Mi sento più fiducioso nella mia decisione ora. :-)
Niklas Wulff il

@NiklasWulff: quindi, i Dtos in questione ora fanno parte del contratto del livello applicazione, ovvero fanno parte del core / dominio. Che dire dei tipi di restituzione dell'API Web ? Esponete i Dtos menzionati nella vostra risposta o avete modelli di visualizzazione dedicati definiti nel livello API Web? O per dirla diversamente: mappate i Dtos per visualizzare i modelli?
Robotron,

1
@Robotron Non disponiamo di API Web, utilizziamo MVC. Quindi sì, associamo dto: s a diversi modelli di vista. Spesso un modello di visualizzazione contiene molte altre cose per visualizzare la pagina Web, quindi i dati di dto: s costituiscono solo una parte del modello di visualizzazione
Niklas Wulff,

4

Finora, utilizziamo modelli di dominio (principalmente entità) su tutti i livelli e utilizziamo DTO solo come modelli di vista (nel controller, i modelli di dominio restituiscono il servizio e il controller crea il modello di vista, che viene passato alla vista).

Poiché il modello di dominio fornisce una terminologia ( Ubiquitous Language ) per l'intera applicazione, è meglio utilizzare ampiamente il modello di dominio.

L'unico motivo per utilizzare ViewModels / DTOs è un'implementazione del modello MVC nell'applicazione per separare View(qualsiasi tipo di livello di presentazione) e Model(Modello di dominio). In questo caso, la presentazione e il modello di dominio sono liberamente associati.

A volte il servizio deve restituire un oggetto dati non definito nel dominio e quindi è necessario aggiungere un nuovo oggetto al dominio non mappato o creare un oggetto POCO (è brutto, poiché alcuni servizi restituiscono modelli di dominio, alcuni restituire effettivamente DTO).

Presumo che tu parli dei servizi di Logica Applicazione / Affari / Dominio.

Ti suggerisco di restituire entità di dominio quando puoi. Se è necessario restituire informazioni aggiuntive, è accettabile restituire DTO che contiene diverse entità di dominio.

A volte, le persone che utilizzano framework di terze parti, che generano proxy su entità di dominio, incontrano difficoltà nell'esporre entità di dominio dai loro servizi, ma è solo una questione di utilizzo errato.

La domanda è: se utilizziamo rigorosamente i modelli di visualizzazione, è corretto restituire i modelli di dominio ai controller o dovremmo sempre utilizzare DTO per la comunicazione con il livello di servizio?

Direi che è sufficiente restituire entità di dominio nel 99,9% dei casi.

Al fine di semplificare la creazione di DTO e la mappatura delle entità del dominio in esse, è possibile utilizzare AutoMapper .


4

Se restituisci parte del tuo modello di dominio, diventa parte di un contratto. È difficile cambiare un contratto, poiché le cose al di fuori del tuo contesto dipendono da esso. Pertanto, renderebbe difficile modificare parte del modello di dominio.

Un aspetto molto importante di un modello di dominio è che è facile da modificare. Questo ci rende flessibili alle mutevoli esigenze del dominio.


2

Suggerirei di analizzare queste due domande:

  1. I tuoi livelli superiori (ovvero visualizzare e visualizzare modelli / controller) consumano i dati in un modo diverso rispetto a quello che espone il livello di dominio? Se sono in corso molte mappature o addirittura una logica, suggerirò di rivisitare il tuo progetto: probabilmente dovrebbe essere più vicino al modo in cui i dati vengono effettivamente utilizzati.

  2. Quanto è probabile che tu cambi profondamente i tuoi strati superiori? (ad es. scambio di ASP.NET per WPF). Se questo è molto diverso e la tua architettura non è molto complessa, potresti essere meglio esponendo quante più entità di dominio possibile.

Temo che sia un argomento piuttosto ampio e dipende davvero dalla complessità del sistema e dai suoi requisiti.


Nel nostro caso, lo strato superiore sicuramente non cambierà. In alcuni casi, il servizio restituisce un oggetto POCO piuttosto unico (costruito da più domini - ad esempio, utente e file di sua proprietà), in alcuni casi un servizio restituisce semplicemente un modello di dominio - ad esempio, il risultato di "FindUserByEmail () dovrebbe restituire il modello di dominio dell'utente - e ecco la mia preoccupazione, a volte i nostri servizi restituiscono il modello di dominio, a volte un nuovo DTO - e non mi piace questa incoerenza, ho letto quanti più articoli possibile e la maggior parte sembra concordare sul fatto che anche se il modello di dominio di mappatura <-> DTO è 1: 1, il modello di dominio non dovrebbe lasciare il livello di servizio - quindi sono strappato
Robert Goldwein

In uno scenario del genere e purché tu possa sopportare lo sforzo di sviluppo aggiuntivo, mi piacerebbe anche la mappatura in modo che la tua stratificazione sia più coerente.
jnovo,

1

Nella mia esperienza, a meno che tu non stia utilizzando un modello di interfaccia utente OO (come oggetti nudi), esporre gli oggetti di dominio all'interfaccia utente è una cattiva idea. Questo perché man mano che l'applicazione cresce, le esigenze dell'interfaccia utente cambiano e costringono gli oggetti ad adattarsi a tali modifiche. Finisci per servire 2 padroni: UI e DOMAIN che è un'esperienza molto dolorosa. Credimi, non vuoi essere lì. Il modello di interfaccia utente ha la funzione di comunicare con l'utente, il modello DOMAIN per contenere le regole di business e i modelli di persistenza si occupano di archiviare i dati in modo efficace. Tutti rispondono a diverse esigenze dell'applicazione. Sto scrivendo un post sul blog a riguardo, lo aggiungerò al termine.

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.