2009-06-16 36 views
12

Richiesta di feedback/opzioni/commenti relativi a un modello "migliore" da utilizzare per i dati di riferimento nei miei servizi.Dati del contratto WCF e dati delle entità di riferimento?

Cosa intendo per dati di riferimento?

Utilizziamo Northwind come esempio. Un ordine è correlato a un cliente nel database. Quando implemento il mio servizio ordini, in alcuni casi desidero che il riferimento sia un cliente "completo" da un ordine e altri casi quando desidero solo un riferimento al cliente (ad esempio una coppia chiave/valore).

Ad esempio, se facessi un GetAllOrders(), non vorrei restituire un ordine completamente compilato, vorrei restituire una versione leggera di un ordine con solo i dati di riferimento per il cliente di ciascun ordine. Se ho fatto un metodo GetOrder(), però, probabilmente vorrei inserire i dettagli del cliente perché è probabile che un consumatore di questo metodo possa averne bisogno. Potrebbero esserci altre situazioni in cui potrei voler chiedere che i dettagli del cliente vengano compilati durante determinate chiamate di metodo, ma lasciati fuori per gli altri.

Ecco quello che è venuta in mente:

[DataContract] 
public OrderDTO 
{ 
    [DataMember(Required)] 
    public CustomerDTO; 

    //etc.. 
} 

[DataContract] 
public CustomerDTO 
{ 
    [DataMember(Required)] 
    public ReferenceInfo ReferenceInfo; 

    [DataMember(Optional)] 
    public CustomerInfo CustomerInfo; 
} 

[DataContract] 
public ReferenceInfo 
{ 
    [DataMember(Required)] 
    public string Key; 

    [DataMember(Required)] 
    public string Value; 
} 

[DataContract] 
public CustomerInfo 
{ 
    [DataMember(Required)] 
    public string CustomerID; 

    [DataMember(Required)] 
    public string Name; 

    //etc.... 
} 

Il pensiero qui è che, poiché ReferenceInfo (che è una coppia chiave/valore generico) è sempre necessario in CustomerDTO, avrò sempre ReferenceInfo . Mi fornisce informazioni sufficienti per ottenere i dettagli del cliente in un secondo momento, se necessario. Lo svantaggio di avere CustomerDTO richiede ReferenceInfo è che potrebbe essere eccessivo quando sto ricevendo l'intero CustomerDTO (cioè con CustomerInfo compilato), ma almeno mi sono garantite le informazioni di riferimento.

C'è qualche altro modello o quadro che posso usare per rendere questo scenario/implementazione "più pulito"?

La ragione per cui chiedo è che, sebbene potremmo semplicemente dire in Northwind di restituire SEMPRE un CustomerDTO completo, che potrebbe funzionare correttamente nella semplicistica situazione di Northwind. Nel mio caso, ho un oggetto che ha 25-50 campi che sono dati di riferimento/tipo di ricerca. Alcuni sono più importanti da caricare rispetto ad altri in situazioni diverse, ma mi piacerebbe avere il minor numero possibile di definizioni di questi tipi di riferimento (in modo da non entrare nel "Danno della manutenzione DTO").

Opinioni? Risposta? Commenti?

Grazie!

+0

FWIW ... Non penso Linq a SQL, Entity Framework o ADO.NET Data Services mi aiuterà qui, poiché ho 3 sistemi diversi, tutti che rappresentano, in sostanza, lo stesso set di dati. Questo è il primo passo verso il consolidamento di questi sistemi, o almeno per i sistemi esterni, per poter utilizzare i dati di questi tre sistemi in modo più generico e coerente. – Brian

+0

Potresti rispondere http://stackoverflow.com/questions/9483286/understanding-data-outside-of-service-soa? – Lijo

risposta

1

Sembra una soluzione complicata per me. Perché non avere un campo ID cliente nella classe OrderDTO e quindi lasciare decidere all'applicazione in fase di esecuzione se ha bisogno dei dati del cliente. Dal momento che ha l'id cliente, può tirare giù i dati quando decide di farlo.

+0

Esporrò la possibilità di ottenere i dati del cliente forniti da un CustomerID, ma cercando di evitare un'interfaccia "chatty". – Brian

+0

Elaborazione di ciò che intendo con l'interfaccia "chatty". Supponiamo che mostri i risultati di GetAllOrders() in una griglia. Una colonna nella griglia è "Nome cliente". Non vorrei chiamare GetCustomer() per ogni ordine che visualizzo nella griglia. Inoltre, non desidero interrompere il contratto se un nuovo requisito indica che oltre al nome cliente è necessario anche visualizzare, ad esempio, il numero di telefono del cliente. Vorrei solo essere in grado di passare la mia chiamata di metodo a qualcosa del genere:
GetAllOrders (expandReference = true); Nota: non sono sicuro se il numero di telefono sia effettivamente in Northwind oppure no. – Brian

+0

Quindi si dovrebbe avere una logica che restituisca dettagli del cliente parziale in base alle coppie chiave-valore in CustomerDTO? Mi ci è voluto un po 'per afferrare il tuo meccanismo che potrebbe essere un segnale troppo complesso o che sono lento. Se fossi in me, sicuramente proverei a farlo solo con due classi: ordine e cliente. Aggiungere un metodo extra all'interfaccia per dire che GetAllOrdersPartial() sembra molto meno male di una creazione di altre classi piuttosto oscure. Se dovessi mantenere il tuo codice, preferirei alcune chiamate di metodo aggiuntive invece di elaborare qualche ottimizzazione ingannevole per lo stesso effetto. – sipwiz

1

Ho deciso contro l'approccio che avrei preso. Penso che molte delle mie preoccupazioni iniziali fossero il risultato di una mancanza di requisiti. Mi aspettavo che fosse così, ma ero curioso di vedere come altri avrebbero potuto affrontare questo problema nel determinare quando caricare certi dati e quando no.

Sto appiattendo il mio contratto dati per contenere i campi di dati di riferimento più utilizzati. Questo dovrebbe funzionare per la maggioranza dei consumatori. Se i dati forniti non sono sufficienti per un dato consumatore, avranno la possibilità di interrogare un servizio separato per recuperare i dettagli completi per una particolare entità di riferimento (ad esempio una valuta, stato, ecc.). Per le ricerche semplici che sono in pratica coppie chiave/valore, le gestiremo con un contratto dati di coppia chiave/valore generico. Potrei anche usare l'attributo KnownType per le mie coppie chiave/valore più specializzate.

[DataContract] 
public OrderDTO 
{ 
    [DataMember(Required)] 
    public CustomerDTO Customer; 

    //in this case, I think consumers will need currency data, 
    //so I pass back a full currency item 
    [DataMember(Required)] 
    public Currency Currency; 

    //in this case, I think consumers are not likely to need full StateRegion data, 
    //so I pass back a "reference" to it 
    //User's can call a separate service method to get full details if needed, or 
    [DataMember(Required)] 
    public KeyValuePair ShipToStateRegion; 

    //etc.. 
} 


[DataContract] 
[KnownType(Currency)] 
public KeyValuePair 
{ 
    [DataMember(Required)] 
    public string Key; 

    [DataMember(Required)] 
    public string Value; 

    //enum consisting of all possible reference types, 
    //such as "Currency", "StateRegion", "Country", etc. 
    [DataMember(Required)] 
    public ReferenceType ReferenceType; 
} 


[DataContract] 
public Currency : KeyValuePair 
{ 
    [DataMember(Required)] 
    public decimal ExchangeRate; 

    [DataMember(Required)] 
    public DateTime ExchangeRateAsOfDate; 
} 


[DataContract] 
public CustomerDTO 
{ 
    [DataMember(Required)] 
    public string CustomerID; 

    [DataMember(Required)] 
    public string Name; 

    //etc.... 
} 

Pensieri? Opinioni? Commenti?

+0

Questo sembra un ragionevole compromesso, ma preparatevi a un po 'di messa a punto e messa a punto fino a quando non scoprirete quale dovrebbe essere il "sottoinsieme comune" delle proprietà. E se questo progetto è principalmente per scopi di reporting o analisi, c'è sempre la possibilità che non ci sarà affatto un sottoinsieme comune. E questo ti riporta di nuovo al bisogno di più serializzazioni di nuovo. – dthrasher

+0

@ Brian- Anche se questa è una vecchia domanda ho aggiunto una risposta su come Amazon lo fa, come potresti trovarlo utile. – RichardOD

2

Siamo allo stesso punto di decisione del nostro progetto. A partire da ora, abbiamo deciso di creare tre livelli di DTO per gestire una cosa: SimpleThing, ComplexThing e FullThing. Non sappiamo come funzionerà per noi, quindi, questa non è ancora una risposta fondata sulla realtà.

Una cosa che mi chiedo è se potessimo imparare che i nostri servizi sono progettati a livello "sbagliato". Ad esempio, c'è mai un'istanza in cui dovremmo interrompere un FullThing e passare solo un SimpleThing? Se lo facciamo, ciò implica che abbiamo impropriamente messo alcune logiche di business a un livello troppo alto?

+0

Ha funzionato per te? Sono più propenso a questo approccio. – Jonn

1

Abbiamo affrontato questo problema anche nel mapping relazionale agli oggetti. Ci sono situazioni in cui vogliamo l'oggetto completo e altri dove vogliamo un riferimento ad esso.

La difficoltà è che per la cottura la serializzazione nelle classi stesse, il modello DataContract rafforza l'idea che c'è solo un modo giusto per serializzare un oggetto. Ma ci sono molti scenari in cui potresti voler serializzare parzialmente una classe e/o i suoi oggetti figli.

Ciò significa in genere che è necessario disporre di più DTO per ogni classe. Ad esempio, un FullCustomerDTO e un CustomerReferenceDTO. Quindi devi creare dei modi per mappare i diversi DTO all'oggetto dominio del Cliente.

Come potete immaginare, è una tonnellata di lavoro, la maggior parte molto noiosa.

+0

Potresti rispondere http://stackoverflow.com/questions/9483286/understanding-data-outside-of-service-soa? – Lijo

1

Un'altra possibilità è quella di trattare gli oggetti come sacchetti di proprietà. Specifica le proprietà che desideri durante l'interrogazione e recupera esattamente le proprietà di cui hai bisogno.

La modifica delle proprietà da mostrare nella versione "breve" non richiede più round trip, è possibile ottenere tutte le proprietà di un set contemporaneamente (evitando interfacce chatty) e non è necessario modifica i tuoi dati o contratti operativi se decidi di aver bisogno di diverse proprietà per la versione "corta".

+0

Trattare le proprietà come oggetti stessi è un'idea che stavo spingendo. Penso che questo avrebbe permesso alla flessibilità di attirare tutte le proprietà che volevo. Ciò detto, introduce un livello di complessità nel sistema in quanto non si tratta più di oggetti che contengono campi di tipo nativo, ma piuttosto una raccolta di "oggetti campo". L'astrazione ti dà flessibilità, ma al costo della complessità (ed eventualmente della manutenibilità). Ho trovato nella mia situazione particolare, la maggior parte degli altri sviluppatori ha trovato questa soluzione troppo complessa da comprendere. – Brian

+1

I due non sono necessariamente esclusivi. Le API pubbliche potrebbero avere campi tipizzati in modo statico, mentre si utilizza un sacchetto di proprietà come implementazione sottostante. Si potrebbe anche esporre (direttamente o indirettamente) la borsa per consentire l'aggiunta dinamica di "campi", pur mantenendo le proprietà statiche per le cose statiche conosciute. – kyoryu

+0

Per essere più specifici, non c'è motivo per cui il contratto WCF debba corrispondere all'API utilizzata dai consumatori dell'oggetto, e in effetti, in genere suggerirei che starai meglio a lungo termine se lo rendi esplicito in anticipo. – kyoryu

2

Il servizio Web API di Amazon Product Advertising è un buon esempio dello stesso problema riscontrato.

Utilizzano diversi DTO per fornire ai chiamanti più o meno dettagli in base alle loro circostanze. Ad esempio c'è lo small response group, lo large response group e nel mezzo medium response group.

Avere DTO diversi è una buona tecnica se come dici tu non vuoi un'interfaccia chatty.

+0

Chiedendosi, come allineare il gruppo di risposta Small, Medium, Large in una gerarchia ereditaria? Gli ORM dovrebbero sempre mantenere un DTO di grandi dimensioni e quindi avere DTO piccoli e DTO medi solo come wrapper? – Jonn

+0

@RichardOD Potresti rispondere http://stackoverflow.com/questions/9483286/understanding-data-outside-of-service-soa? – Lijo

1

Generalmente, carico in modo lazy i miei complessi servizi Web (ovvero i servizi Web che inviano/ricevono entità). Se una persona ha una proprietà padre (anche una persona), invio solo un identificatore per il padre anziché l'oggetto nidificato, quindi mi limito ad assicurarmi che il mio servizio web abbia un'operazione che possa accettare un identificatore e rispondere con l'entità persona corrispondente . Il client può quindi richiamare il servizio Web se desidera utilizzare la proprietà Padre.

Ho anche espanso su questo in modo che possa verificarsi il batching. Se un'operazione restituisce 5 persone, se si accede alla proprietà di Padre su una di queste persone, viene effettuata una richiesta per tutti i 5 padri con i loro identificatori. Questo aiuta a ridurre la chattiness del servizio web.

+0

Nota: l'utilizzo di questa strategia richiede un'acuta consapevolezza dell'identità dell'entità.Non puoi volere più istanze della stessa Entità mobile; quindi potresti voler creare una cache sul client delle entità che hai ricevuto fino ad ora e usare quelle che ti sono state date per tornare indietro per un'altra copia di esse. –

Problemi correlati