È difficile stabilire le migliori pratiche per qualcosa di "flessibile" o astratto come un DTO. In sostanza, i DTO sono solo oggetti per il trasferimento di dati, ma a seconda della destinazione o del motivo del trasferimento, è possibile che si desideri applicare diverse "migliori pratiche".
Consiglio di leggere Patterns of Enterprise Application Architecture di Martin Fowler . C'è un intero capitolo dedicato ai modelli, in cui i DTO ottengono una sezione davvero dettagliata.
Inizialmente, erano "progettati" per essere utilizzati in costose chiamate remote, dove probabilmente avresti bisogno di molti dati da diverse parti della tua logica; I DTO effettuerebbero il trasferimento di dati in una singola chiamata.
Secondo l'autore, i DTO non erano destinati ad essere utilizzati in ambienti locali, ma alcune persone hanno trovato un uso per loro. Di solito vengono utilizzati per raccogliere informazioni da diversi POCO in un'unica entità per GUI, API o livelli diversi.
Ora, con l'ereditarietà, il riutilizzo del codice è più simile a un effetto collaterale dell'eredità piuttosto che al suo obiettivo principale; la composizione, d'altra parte, è implementata con il riutilizzo del codice come obiettivo principale.
Alcune persone raccomandano l'uso della composizione e dell'eredità insieme, usando i punti di forza di entrambi e cercando di mitigare i loro punti deboli. Quanto segue fa parte del mio processo mentale quando scelgo o creo nuovi DTO, o qualsiasi nuova classe / oggetto per quella materia:
- Uso l'ereditarietà con DTO all'interno dello stesso layer o stesso contesto. Un DTO non erediterà mai da un POCO, un DTO BLL non erediterà mai da un DTO DAL, ecc.
- Se mi trovo a cercare di nascondere un campo a un DTO, rifoterò e magari userò la composizione.
- Se sono sufficienti pochissimi campi diversi da un DTO di base, li inserirò in un DTO universale. I DTO universali vengono utilizzati solo internamente.
- Un POCO / DTO di base non sarà quasi mai usato per nessuna logica, in questo modo la base risponde solo ai bisogni dei suoi figli. Se mai dovessi usare la base, evito di aggiungere nuovi campi che i suoi figli non useranno mai.
Alcuni di loro forse non sono le "migliori" pratiche, funzionano abbastanza bene per i progetti a cui sto lavorando, ma è necessario ricordare che nessuna dimensione si adatta a tutti. Nel caso del DTO universale dovresti stare attento, le firme dei miei metodi si presentano così:
public void DoSomething(BaseDTO base) {
//Some code
}
Se uno qualsiasi dei metodi ha mai bisogno del proprio DTO, faccio l'ereditarietà e di solito l'unica modifica che devo fare è il parametro, anche se a volte ho bisogno di scavare più a fondo per casi specifici.
Dai tuoi commenti ho capito che stai usando DTO nidificati. Se i tuoi DTO nidificati consistono solo in un elenco di altri DTO, penso che la cosa migliore da fare sia scartare l'elenco.
A seconda della quantità di dati che è necessario visualizzare o utilizzare, potrebbe essere una buona idea creare nuovi DTO che limitano i dati; ad esempio, se il tuo UserDTO ha molti campi e hai bisogno solo di 1 o 2, potrebbe essere meglio avere un DTO con solo quei campi. Definire il livello, il contesto, l'utilizzo e l'utilità di un DTO aiuterà molto nella progettazione.