Data Transfer Object, questo sconosciuto…
Molto spesso capitano domande, sui forum, via email o di persona, su come è possibile far viaggiare le proprie entity con WCF. Molti non sono disposti a marcare con gli attributi DataContract e DataMember, e direi anche giustamente. Il .NET Framework 3.5 consente anche di utilizzare oggetti senza la necessità di marcarli con gli appositi attributi.
Il punto però è un altro. Spesso la necessità non è quella di trasferire le entità, bensi un insieme probabilmente differente contenente informazioni specifiche e ben definite. Una ipotetica entità Book, nel nostro dominio, ha sicuramente un riferimento ad una entità Author. Probabilmente avremo qualcosa del tipo:
public class Book
{
public string ISBN { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public int Pages { get; set; }
public double Price { get; set; }
public Author Author { get; set; }
}
public class Author
{
public string Name { get; set; }
}
Abbiamo semplificato parecchio. Se il nostro servizio esponesse l’entità Book così com’è saremmo costretti a generare un messaggio con dati probabilmente non necessari per lo specifico contesto. Perchè trasferire tutto? E se la gerarchia fosse ancora più complessa? Performance, processo di serializzazione, dimensioni del messaggio, tutto ne pagherebbe le conseguenze, inutilmente.
Il Data Transfer Object
Ancora una volta i pattern ci vengono in aiuto. Il Data Transfer Object (DTO, talvolta conosciuto anche come Value Object) è un oggetto che non appartiene al nostro dominio, contenente solo proprietà e nessun metodo o logica, ma soprattutto è un oggetto serializzabile ed ottimizzato per il semplice scopo per cui è stato creato: trasferimento di dati.
Nel nostro esempio avremmo dunque qualcosa di simile:
public class BookDTO
{
public string Title { get; set; }
public string AuthorName { get; set; }
public double Price { get; set; }
}
Semplicemente quello che deve essere trasferito. Niente di più. Di conseguenza il contratto del nostro servizio risulterebbe quindi questo:
[ServiceContract]
public interface BookService
{
[OperationContract]
public BookDTO RetrieveBook(string isbn);
}
L‘implentazione del servizio ed il mapping dei dati
A questo punto il servizio si dovrebbe semplicemente occupare di invocare lo strato successivo, recuperare l’istanza dell’oggetto Book e poi restituirla al chiamante:
public BookDTO RetrieveBook(string isbn)
{
Book book = OtherLayer.GetBook(isbn);
BookDTO bookDTO = Map(book);
return bookDTO;
}
Generalmente preferisco avere un servizio WCF che si occupa semplicemente di trasformare o adattare le esigenze di integrazione del mio dominio con il mondo esterno. Quindi pochissima logica, solo validazione dei dati e generazione di eventuali eccezioni (scordatevi l’accesso al database direttamente dal servizio
). Nel servizio quindi viene fatto il mapping del dominio verso l’entità esterna utilizzando logiche ad-hoc oppure appositi mapper. Il pattern DTO, come definito da Martin Fowler, prevere la realizzazione di un apposito componente chiamato assembler che “assembla” le informazioni e crea il DTO (o l’entità, a seconda della direzione). L’assembler deve essere sviluppato appositamente per ogni oggetto del dominio.
Preferisco, invece, l’utilizzo di un mapper che automaticamente mappa le entità a seconda delle esigenze. AutoMapper è decisamente un’ottimo punto di partenza. Consente infatti di eseguire il mapping di oggetti utilizzando specifiche regole. Nel nostro caso la funzione Map sarebbe semplicemente così composta:
private BookDTO Map(Book sourceBook)
{
Mapper.CreateMap<Book, BookDTO>();
return Mapper.Map<Book, BookDTO>(sourceBook);
}
AutoMapper effettuerà per noi la creazione di un istanza di BookDTO mappando le proprietà Title e Price da Book, e la proprietà AuthorName dalla proprietà Name della classe Author. Questo perchè nel momento in cui non trova nessuna proprietà con il nome della proprietà di destinazione e nessun metodo chiamato GetNomeProprietà, tenterà di suddividere il nome della proprietà di destinazione utilizzando il PascalCase per trovare il valore da assegnare (spero di essere stato chiaro
, altrimenti ditemelo…).
Qual’è il vantaggio? Non devo scrivere quintali di righe di codice per ogni mapping che devo eseguire. E non è mica poco se il mio dominio è decisamente complesso.
Conclusioni
Sono uno dei fan del DTO. Collegandomi al post precedente, la sequenza ottimale da seguire per la realizzazione di un buon strato di servizi è questa:
- Definizione degli schema XSD
- Definizione dei messaggi
- WSDL
- Generazione del codice. Che si suddivide poi in:
- Creazione dei DTO (partendo dagli XSD)
- Creazione dell’interfaccia del servizio (in parole povere l’interfaccia che marcheremo con ServiceContract e OperationContract)
- Collegamento con il layer successivo, che comunica utilizzando gli oggetti del dominio e quindi generazione del mapping bidirezionale DTO <—> Domain Object
Vedo in questo approccio un’ottima suddivisione della logica del mio dominio con la logica di integrazione con i sistemi esterni. Un sistema esterno non è solo e necessariamente un’applicazione sviluppata da terze parti, ma anche una qualsiasi applicazione sempre da noi sviluppata e che fa parte della nostra soluzione, come ad esempio può essere un’applicazione Silverlight.
Apertissimo alla discussione 