DTO sono importanti nell'azione ASP.NET MVC JSON?

3

Recentemente mi sono imbattuto in un progetto interno in cui utilizzavano DTO per tutta la soluzione, e quando mi sono seduto per implementare un nuovo controller MVC con un endpoint JSON REST ho deciso di utilizzare una convenzione anonima per gli oggetti invece di dichiarare una discreta classe DTO esclusivamente ai fini di questo endpoint dell'API.

public JsonResult GetSomeSuch()
{
    // .. do work

    return Json(new {
        PropA = domainObj1.PropA,
        PropB = domainObj2.PropB
    });
}

.. in contrasto con la convenzione del team, ..

public JsonResult GetSomeSuch()
{
    // do work ..

    return Json(new SomeSuchDTO2 {
        PropA = domainObj1.PropA,
        PropB = domainObj2.PropB
    }
}

// in some other file off in some other project and folder
public class SomeSuchDTO2 {
    public int PropA { get; set; }
    public string PropB { get; set; }
}

Mettendo da parte che avrei dovuto usare un DTO qui solo perché era già una convenzione di squadra, le convenzioni del team vincono le migliori pratiche fino a quando le convenzioni di squadra cambiano a seconda della leadership del team, di cui non ero ancora parte. Non sono preoccupato per gli aspetti sociali / di squadra e di coerenza, ma anche per il valore tecnico dei dettagli nello specifico punto in cui un'azione in C # diventa serializzata in Javascript, come da allora ho spostato da quella società.

La convenzione del team era di restituire solo DTO e le DTO sono state gestite in un progetto completamente diverso (tra circa 50 progetti nella soluzione), ho sentito che in questo caso perpetuare quel modello era eccessivo e difficile da mantenere. Inoltre, credo che il DRY debba essere correlato con YAGNI; solo refactoring quando stai ripetendo la stessa cosa due volte, in quanto tale ti rifatteresti solo per usare un DTO se quella stessa struttura dell'oggetto di ritorno che riflette lo stesso contesto è in realtà necessaria altrove, non solo forse ma in realtà, ma dato che viene serializzato in Javascript, dove verrà comunque consumato, non c'è davvero alcun punto.

Suppongo che si possa creare un caso per DTO come documentazione delle specifiche, ma quando diventa più un compito di manutenzione dichiararlo e cercarlo nella loro cartella speciale in un progetto, direi che ci sono posti più appropriati e significa documentare l'interfaccia; infatti la dichiarazione anonima dell'oggetto sembra essere abbastanza documentata. Dovrei anche aggiungere che nel nostro caso tutto era interno, questo era un progetto autonomo, quindi non è come se si trattasse di un'API esposta esternamente.

Mi stavo chiedendo se ci sono altri punti di vista su questo che non ho considerato il motivo per cui dovrei usare DTO, qualcosa di abbastanza convincente?

    
posta stimpy77 06.12.2013 - 19:30
fonte

4 risposte

4

Un sistema di tipo strong dovrebbe aiutarti

L'uso di un linguaggio strongmente tipizzato ha lo scopo di prevenire errori durante il runtime catturandoli in fase di compilazione. L'uso di un oggetto anonimo (o un dizionario per esempio) sconfigge questo scopo e consente la compilazione di bug nel programma che verrà visualizzato solo durante il runtime.

Ad esempio, dai un'occhiata al codice qui sotto:

public object GetUsers() {
    var users = MyRepo.GetAll<User>();
    return new {
        count: users.Count(),
        items: users
    }
}

public object GetNewUsers() {
    var users = MyRepo.GetNewest<User>();
    return new {
        Count: users.Count(),
        items: users
    }
}

La prima risposta utilizza un parametro count in minuscolo dove la seconda risposta contiene un parametro Count in maiuscolo. In una classe in cui questi metodi possono essere separati da pochi blocchi di codice è molto facile perdere questo errore di battitura. Ancora peggio, questo sarà felicemente compilato e gestito. Se dovessimo usare un DTO dattilografato, questa situazione non sarebbe possibile (in effetti non sarebbe nemmeno compilabile).

O che ne dici di questo esempio:

public object GetSystemInstallDate(){
    var date = AppHost.GetInstallDate();
    return new { 
        date
    };
}

Cosa succede se refactoring modifica il metodo GetInstallDate per restituire un DateTimeOffset invece di un DateTime oggetto? Il codice sarà felicemente compilato ma l'output per il client sarà cambiato. Questo è un esempio debole, ma ci sono molti altri casi in cui cose come questa possono accadere. Tipi numerici, refactoring enum, ecc ...

refactoring

Se la tua risposta anonima è utilizzata in più di una posizione, non potrai refactoring usando gli strumenti di fantasia come il programma di ricerca. Dovrai refactoring manuale che non è mai un compito divertente. Se viene utilizzato in una singola posizione, il refactoring è più semplice ma ancora più utile rispetto alla definizione della classe, in primo luogo.

DRY

Direi che usando i tipi anonimi non ti stai proteggendo dalla violazione di DRY; piuttosto, ti stai rendendo più propenso a violarlo. Un tipo anonimo può essere utilizzato solo in un posto; tuttavia, se ciò che rappresenta rappresenta sempre richiesto altrove, l'intero tipo dovrà essere ricreato.

Usando un oggetto strongmente tipizzato, devi solo definire il tipo in un singolo posto e quindi usare quel tipo dove è necessario. Questo ti impedisce di dover scrivere tutti i nomi delle proprietà, i valori di formattazione, ecc ...

public object GetTop10Movies() {
    return new { lastmodified = DateTime.Now, items }
}

public object GetNewest10Movies() {
    // We now have to repeat the previous anonymous object
}

YAGNI

Questa è una linea guida e non una regola. Se fosse una regola si ridurrebbe a "ignorare il futuro", che è sempre una ricetta per il disastro. Allo stesso tempo, cercare di coprire tutti i possibili scenari futuri è un'idea orribile e porterà a un codice base troppo astratto e complicato. Il trucco per trovare il giusto equilibrio tra pensare in anticipo e risolvere i problemi a portata di mano.

Un altro modo di pensare YAGNI in questa situazione è: hai bisogno di compilare la sicurezza del tipo orario?

Direi che il lavoro coinvolto nella creazione di una classe per rappresentare la tua risposta è solo leggermente più del lavoro necessario per digitare un oggetto anonimo. La differenza di complessità è essenzialmente nulla (in effetti con il mondo C # un oggetto anonimo sarebbe molto probabilmente considerato una più complessa rotta da prendere).

Per un investimento di tempo piuttosto minimo si ottengono alcuni vantaggi reali e utilizzabili dallo sviluppatore. Potrebbero non essere necessari nel senso che senza di essi l'applicazione non funzionerà, ma se non avessi bisogno di un linguaggio strongmente tipizzato metterei in discussione l'uso di C # in primo luogo.

Manutenzione

The team convention was to return only DTOs and the DTOs were managed in a completely different project (among about 50 projects in the solution), I felt that in this case perpetuating that pattern was excessive and difficult to maintain.

Non sono sicuro di dove arriverebbe la difficoltà nel mantenere questo schema, tranne che per l'idea di avere 50 progetti tutti raggruppati in un'unica soluzione. Avrai una delle due situazioni:

  1. È necessario un nuovo tipo di risposta.

Aggiungi un nuovo file al progetto DTO che rappresenta il tuo tipo e poi usalo nella tua applicazione (dovrebbe esistere un riferimento tra i progetti).

  1. È necessario un tipo di risposta esistente.

Inizia a digitare lo spazio dei nomi dell'assieme DTO e seleziona il tipo di cui hai bisogno.

IsupposeacasecanbemadeforDTOsasspecificationdocumentation,butwhenitbecomesmoreofamaintenancechoretodeclarethemandtolookthemupintheirspecialfolderinaprojectIwouldarguethattherearemoreappropriateplacesandmeanstodocumenttheinterface;indeedtheanonymousobjectdeclarationseemstobedocumentationenough.

Nonc'èpostomiglioreperdocumentareiltuocodicepiuttostocheavereiltuostessocodice.IlnomedellatuarispostaDTOdovrebbedescriveredicosasitratta.Latuadichiarazioneanonimanondicenulladiciòcherappresenta,tidicesolociòchecontiene.Dovercercareladocumentazioneesterna(comeunsitoWebounfilediaiuto)èancorapiùcomplicato.

Unascatolacheconsistediduebulloni,duerondelleeundadonondescriveaffattoacosaserve.Unascatolaconl'adesivo"monitor wall mount" ti consente di sapere esattamente di cosa si tratta semplicemente guardando la confezione.

new { id, model }
// What is this? A vehicle? An economic model? Representation of DNA?

new Vehicle { id = id, model = model }
// Oh! A vehicle!
    
risposta data 07.12.2013 - 00:10
fonte
3

Personalmente, sono passato da JsonResults anonimi a DTO. Ho adorato gli oggetti anonimi - sono veloci, facili, non fanno davvero del male a nessuno (trasferendo Json dal server all'IU, non mi interessa molto del controllo dei tipi ... questo è solo un veicolo di serializzazione). In un ambiente agile ... per me va bene.

Nel corso del tempo, tuttavia, ho notato che quando si restituiscono dati, soprattutto a un'interfaccia utente, spesso non è necessario un po 'di formattazione / conversione / elaborazione standardizzata. Ho iniziato a vedere il codice duplicato per eseguire la formattazione di data / ora, le conversioni di unità di misura, ecc. Con DTO, posso applicare un po 'di standardizzazione su queste cose tramite interfacce core e alcuni metodi di estensione. Quindi stiamo parlando del riutilizzo, ma non del DTO stesso.

Certo - ci sono casi in cui non ha senso. Se stai inviando una tupla di alcuni interi, a chi importa. Tuttavia, penso che sia più facile per una squadra attenersi semplicemente alla regola "creare DTO per le azioni Json" e fornire benefici a lungo termine. Il costo è economico.

    
risposta data 13.12.2013 - 18:56
fonte
2

Mi piace utilizzare il tipo anonimo in questo caso perché:

  1. Questo è il passaggio finale della trasformazione dei dati sul server. Un sistema di tipi aumenta il carico per i programmatori senza aggiungere alcun valore all'operazione. Riguardo al strong client dattilografato menzionato da Mike, in realtà vedo che causa più problemi nel mondo reale - l'accoppiamento tra il progetto di servizio e tutti i progetti dei clienti tende ad aumentare nel tempo.

  2. I tipi anonimi non inquinano lo spazio dei nomi. Non sottovalutare il vantaggio di questo. In un progetto in cui gli sviluppatori vanno e vengono e il business cambia spesso idea, uno sviluppatore tende a scegliere un nome fuorviante per il nuovo DTO che deve creare se viene preso il "buono", il che rende entrambi i nomi fuorvianti per il successivo manutentori.

risposta data 13.12.2013 - 22:07
fonte
2

Dicono che venire con i nomi è difficile , ma credo che sia difficile solo perché troppi programmatori idealisti ti insidiano dovrebbe sempre estrarre la dichiarazione dall'uso quando possibile. Quella stupida regola ti impone di nominare quelle dichiarazioni estratte, e quando l'unica ragione per estrarle è obbedire a quella regola, è difficile nominarle. Anche se la lingua ti obbligasse comunque a usare una dichiarazione, quella regola ti obbliga a spostare la dichiarazione da qualche parte lontano dall'utilizzo, impedendoti così di usare il contesto dell'uso come riferimento implicito mentre scegli un nome, qualcosa che potrebbe hanno reso l'operazione di denominazione molto più semplice.

Com'è stato quello relativo alla tua domanda? Quando medito se estrarre la dichiarazione dall'uso o meno, trovo che una grande regola empirica sia considerare quale nome darai alla cosa che dichiari. Se il nome viene naturale, significa che ha un significato nel dominio e dovresti renderlo un DTO. Se devi lottare per trovare un nome e tutto ciò che puoi fare è qualcosa direttamente correlato all'utilizzo (come <methodname>_result ) o una descrizione diretta dei campi all'interno di 1 (come IntAndStringTuple ), non dovresti renderlo un DTO a meno che qualcosa nel progetto non sia costruito in un modo che ti costringa a usare DTO.

Nel tuo caso, il metodo è denominato GetSomeSuch e il DTO è denominato SomeSuchDTO2 . Questi non sono i veri nomi usati nel progetto, ma li userò per l'esempio. Se SomeSuchDTO2 prende il nome da GetSomeSuch - cioè, se senza GetSomeSuch non c'è un modo diretto per capire cosa significhi SomeSuchDTO2 - allora non dovresti creare un DTO. Ma se GetSomeSuch prende il nome da SomeSuchDTO2 - cioè, se SomeSuch è una cosa - allora dovresti creare un DTO.

1 Quando dico "descrizione diretta dei campi", intendo la descrizione dei campi che compongono l'oggetto piuttosto che la descrizione dell'oggetto nel suo insieme. Ad esempio: PersonName è non una descrizione diretta dei campi. anche se puoi capire quali sarebbero i campi da esso, descrive l'oggetto nel suo complesso. Una descrizione diretta dei campi per la stessa classe sarebbe qualcosa come FirstNameAndLastName .

    
risposta data 13.12.2013 - 22:42
fonte

Leggi altre domande sui tag