Object transport

 
26 januari 2008

Inmiddels ben ik in heel wat applicaties een vorm van object transport tegengekomen. Bepaalde objecten uit een business-logica laag of component dienen getransporteerd te worden vanaf of richting een user interface, email component of database. Wat heel vaak gedaan wordt is transport via (web)services richting een user interface waarbij de services niet de ‘echte’ business objecten vervoeren maar DTO’s (Data Transfer Objects). Aangezien logica of gedrag van deze objecten niet meegestuurd wordt over deze services zie je dat in de targetlaag vaak bepaalde logica die al bestond in de bronlaag opnieuw declareert.

Kort samengevat in een fout plaatje ziet het er als volgt uit:

Wat bovenstaand plaatje illustreert is dat bij deze redelijk recht-toe-recht-aan architectuur er 4 vertalingsmomenten zijn om je object uit een database richting een presentatie te krijgen in een user interface. Ik zou in deze post dan ook willen proberen een oplossing te schetsen waardoor er totaal geen vertalingen meer nodig zijn voor deze stappen, althans niet in (handgeschreven) code. De oplossingsrichting die ik hiervoor kies is een vorm van custom XML Serialisatie. Hiertoe zal ik eerst kort even de verschillende vormen van vertaling langslopen om hierbij wat zaken in context van XML serialisatie aan te tekenen.

Vertalingen
Objects -> XML
Nogal wiedes zou je denken, zodra ik objecten over services ga versturen is dat per definitie geserialiseerd. Volledig correct, echter out-of-the-box XML serialisatie in bijvoorbeeld .NET (in Java heb ik het niet kunnen vinden, behalve via 3rd party libraries) biedt niet de oplossing die je zoekt. Als ik een netwerk van samenhangende business objecten ga serialiseren zonder vertalingsslag richting DTO dan loop ik al vrij snel tegen vertalingsproblemen van object georienteerde structuren naar XML structuren aan: circulaire referenties bijvoorbeeld. Neem een klant met orders, waarvan het order object ook weet bij welke klant die hoort. De XML wordt dan als volgt:

<klant>
  <orders>
    <order>
      <klant>
        <orders>
            ………..

Het ligt voor de hand om dit via een ID/IDREF structuur te doen (iets dat bijvoorbeeld de serializers binnen WCF standaard ondersteunen), maar hiermee lossen we het probleem nog niet op dat we een veel te grote objectboom over de lijn gaan trekken aangezien de out-of-the-box serializers alle geassocieerde objecten meetrekken. Ik wil dus een mechanisme om objectbomen te kunnen opknippen en later on-demand alsnog op te kunnen halen (een vorm van lazy-loading dus).

XML -> Objects
De andere kant op dan. Leuk, zo’n XML bericht, maar hoe maak ik daar nou een gedragsrijk object van? Hier komt een deployment issue kijken en een stukje configuratie. De definitie van bussiness objecten moet in elke laag aanwezig zijn. Voor .NET systemen betekent dit dat een assembly met domain classes in elke laag aanwezig dient te zijn. Deze distributie is echter maar éénmalig noodzakelijk, bij deployment van het systeem (en uiteraard versie updates). Een meer extremere vorm zou mogelijk zijn door enkel een interface definitie te verspreiden en binair te gaan serialiseren, wellicht nog een keer onderwerp van discussie voor een andere post.

Objects -> Tables
Een al veel meer uitgekauwd onderwerp is mapping van objecten naar database stucturen. (N)Hibernate is hier een goed voorbeeld van, en Microsoft komt nu met het Entity Framework. Java heeft de o.a. JPA (Java Persistency Api). Ik maak hier dan ook maar niet teveel woorden aan vuil, echter ben ik wel van mening dat het in de meeste gevallen onzin is om mappings te definieren (of genereren). Persoonlijk ben ik de mening toebedeeld dat er enkel reden is tot mapping definitie als er een bestaande database is, anders kan je volstaan door je domain model direct om te zetten in een tabelstructuur (een van de vele pogingen hiertoe is te vinden op codeplex).

Voorstel tot oplossing
Ok, genoeg geklaagd, hoe lossen we zoiets nou mogelijk op? Ik geef hier een richting aan waar ik graag over wil discussieren, dus brand los in de comments op deze post als je het ergens niet mee eens bent…

We beginnen met een uitbreiding van onze business classes om uniek te kunnen identificeren. Nodig voor database opslag, maar ook voor transport zoals we later zullen zien. Ik definieer hiertoe de interface IUniqueIdentifyable die een get/set van een Guid Id vereist. Tevens wil ik dat objecten geinteresseerden kunnen notificeren van eventueel veranderde fields (ik doe zowiezo alles op fieldniveau qua data, aangezien properties business-specifieke logica kunnen bevatten en daar wil ik duidelijk onder gaan zitten). De andere kant op wil ik classes kunnen bevragen om bepaalde objecten op te zoeken (een repository achtig patroon). Om deze drie zaken te ondersteunen definieer ik de volgende drie generic static events per class:

  • ObjectChanged(T changed)
  • T ObjectRequested(Guid id);
  • List<T> ObjectsRequested(string expression, int start, int amount)

Waarom via events? Het antwoord is ontkoppeling. Als ik een class direct laat interacteren met bijvoorbeeld een database component dan creeer ik een afhankelijkheid. Door op deze manier te werken kan ik in elke laag afzonderlijk de events knopen aan subscribers die de daadwerkelijke acties uitvoeren. Je kunt je voorstellen dat een objectrequested event in de UI laag resulteert in een webservice call richting de business laag als in bovenstaand plaatje. In de businesslaag op zijn beurt wordt het event op dezelfde class doorgegeven aan een databasecomponent die het object uit de bijbehorende tabellen vist.

De keuze om deze events static te maken – dus eenmalig per class per laag te definieren – is vrij arbitrair. Je zou er ook voor kunnen kiezen losse repositories te definieren of bijvoorbeeld collecties te gebruiken die de events gooien, maar in mijn optiek is dit een vrij schone oplossing. Een punt van aandacht is echter wel de un-subscription van de static events, aangezien eventuele subscribers een referntie hebben naar een static event zullen deze wel levenslang in het geheugen blijven. Un-subscription van events zou bijvoorbeeld in een global.asax geregeld kunnen worden in een webcontext.

So far so good, maar we hebben nog wat exta eisen gesteld aan de XML serialisatie van de objecten. Om even conceptueel op een rijtje te krijgen wat we op welke manier zouden willen kunnen serialiseren beschouw ik de volgende situaties:

  • Primitive types – int, string, etc., maar ook bijvoorbeeld eigen gedefinieerde structs (value types).
  • Domain types – IUniqueIdentifyable classes
  • Collections – IUniqueIdentifyable collections

De eerste soort, primitive types, kan ik vaak wel typed serializeren. Aangezien ik hier ook value object onder schaar (niet-IUniqueIdentifyables welteverstaan) zou ik het toch handig vinden als ik deze objecten als geheel over kan sturen. Ik introduceer dus een extra optie ‘binairy’. D.w.z. serialisatie van het object door binnen de xml serialization slag bepaalde objecten als byte[] over te sturen via een binaryformatter geserialiseerd.

De tweede soort zou ik via de huidig te maken manier moeten kunnen (de-)serializeren. Een van de eisen is echter dat ik bomen wil kunnen opknippen: hiervoor introduceer ik dus een “lazy” optie. Handig dat ik nu een Id heb, aangezien ik die kan serializen en aan de andere kant de getter van het field kan aansluiten op het ObjectRequested(Id) event van de betreffende class. Dit is overigens wel iets waar ik momenteel een stukje AOP voor gebruik.

De derde soort, lijsten van IUniqueIdentifyable classes hoef ik niets voor te introduceren. Bij de lazy optie moet ik nu echter een lijst van GUID’s serializen i.p.v. een enkele.

Om mijn domainclasses nu te kunnen serializen creeer ik een custom attribute die mij helpt de metagegevens voor serialization toe te voegen.

public enum DomainSerializationOption
{ Ignore, Binary, Lazy, String, Typed }
public class DomainSerializationOptionsAttribute:Attribute
{
 
DomainSerializationOption option;
 
public DomainSerializationOptionsAttribute(DomainSerializationOption option)
  {
   
this.option = option;
  }
 
public DomainSerializationOption Option
  {
   
get { return option; }
  }
}

Dit stelt mij in staat mijn fields te annoteren met een bepaalde optie. De Ignore optie heb ik geintroduceerd voor zaken die niet interessant zijn om aan de andere kant van het kanaal beschikbaar te hebben.
Aangezien deze post nu eigenlijk al wat langer wordt dan de bedoeling was zal ik de custom (de-)serialization code niet toevoegen hier, maar het is wellicht illustratief om een geserializeerd object door te nemen:
<?xml version=”1.0″ ?>
<Employee>
 
<name serialization=”binary length=”40“>
   
<![CDATA[ AAEAAAD/////AQAAAAAAAAAGAQAAAARoZW5rCw== ]]> f(clean);  
  </name>
 
<address serialization=”typed“>empty</address>
 
<taxnumber /> 
  <function /> 
  <birthdate serialization=”typed“>2008-01-20T21:14:03.15625+01:00</birthdate> 
  <activities serialization=”lazy ids=”16d528eb-469c-4c87-aa9d-dadac047b587,0fb10f8f-e442-4d6e-b815-fbf0ced55292” /> 
</Employee>

Verschillende vormen van de serialisatie zijn hier toegepast. Door standaardisatie van de business object definitie over alle lagen heen, in combinatie met een lazy loading faciliteit in alle lagen is een ultieme vorm van distributie van het domeinmodel bewerkstelligd. Sterker nog, het maakt niet meer uit hoeveel lagen je ertussen zou willen hebben – ze zijn allen ultiem ontkoppeld.

Critici van de ‘puristische’ domain driven design school roepen echter vaak dat domain objecten nooit over een boundary mogen bestaan. Presentatielagen zullen ook altijd een andere partitie in objecten verwachten dan het business domein. Ik ben hier echter tegen – ik denk dat business domein objecten juist een goede representatie zouden moeten zijn om ook het gebruikersproces te ondersteunen. Als er echter ‘vreemde’ kunstgrepen gedaan moeten worden in de UI staat dat je ook in deze structuur natuurlijk vrij, echter het wordt dan wel beperkt tot die laag. Communicatie tussen de verschillende componenten gaat gewoon via de business object definitie.

In mijn optiek is het zelfs mogelijk om via deze structuur hybride oplossingen te maken – met lagen of componenten in zowel Dotnet als in Java. C# en Java zijn zodanig identiek dat het in mijn optiek mogelijk moet zijn om objecten geserialiseerd in .NET te de-serialiseren in Java en vice versa. Ik nodig de Javanen onder ons dan ook uit hierover mee te denken.

Mochten er nu nog lezers over zijn, graag reactie hierop! Code is overigens ook op verzoek beschikbaar.
     


Werken met ?
Kijk dan bij onze mogelijkheden voor zowel starters als ervaren engineers.


Categorieën: Development

Tags: , , ,


Reacties (5)

  • Tja, definieer het schrijven van een eigen protocol. Uiteraard moet je je zoveel mogelijk op bestaande standaarden en infrastructuren baseren – wat ik in deze post beoogde te bereiken was een voorstel voor een standaard strategie / pattern om communicatie over verschillende lagen juist flexibeler en meer standaard te maken.

    Protocol is vaak een woord dat gebruikt wordt voor meer low-level elementen in de infrastructuur. Het meest low-level wat je in mijn optiek zou moeten aan (willen) passen in de standaard zou dan je serialisatie strategie zijn. Over het nut en de noodzaak hiervan zou je kunnen discussieren.

    Ben het wel met je eens dat als je hier te ingewikkelde framework achtige structuren voor gaat bedenken je snel verzandt in een ondoorzichtig geheel dat juist de beoogde flexibiliteit teniet doet. Het is een dunne lijn laten we maar zeggen…

    Geplaatst op 21 februari 2008 om 10:01 Permalink

  • Thomas Zeeman schreef:

    De naam MQ is al gevallen, dus zou ik denken dat JMS dan een logische keuze is om te communiceren vanuit de Java kant. Hoef je geen native libraries te gebruiken waar dat niet nodig is en JMS is in diverse implementaties niet veel meer dan een Java schil om een bestaande MQ implementatie.

    Ik ben verder van mening dat zelf een communicatie protocol voor dit soort dingen schrijven het laatst moet zijn wat je wil doen. Op de korte termijn levert het je misschien winst op in flexibiliteit, maar op de langere termijn zie ik onderhoudbaarheid lastig worden en diezelfde flexibiliteit meestal veranderen in inflexibiliteit.

    Geplaatst op 17 februari 2008 om 9:45 Permalink

  • Ivo Limmen schreef:

    Als Javaan vind ik native libraries helemaal niks. Dit is niet iets van mij maar velen Javanen met mij.
    Er zijn overigens zat protocollen die standaard zijn waarmee Java met .NET kan praten (en visa versa).
    Is JSON (http://www.json.org/) niet iets wat we moeten bekijken? Of Hessian (http://www.hessiancsharp.org/) dat origineel in Java geïmplementeerd is maar nu ook in .Net beschikbaar is. Overigens is Java RMI ook erg snel en het protocol is erg simpel en volgens mij ook wel te porten naar .NET.

    Geplaatst op 09 februari 2008 om 21:34 Permalink

  • Helemaal mee eens, Ivo. Probleem alleen met remoting is dat het niet open is en dat remoting in principe ‘deprecated’ is. Zie: http://msdn2.microsoft.com/en-us/library/aa730857.aspx

    De bedoeling is dat remoting achtige scenario’s via WCF gaan lopen – een ontwikkeling waar ik me wel in kan vinden. Echter de standaard serializatie mogelijkheden die WCF gebruikt zijn in mijn optiek niet voldoende om dit op een goede manier te ondersteunen, vandaar deze post, en wat ik ook in mijn achterhoofd heb is interoperabiliteit met andere platformen en layers (ORM zou je hier ook op kunnen baseren).

    Misschien in dit licht ook interessant: java native interface library for msmq opensource on codeplex.

    Geplaatst op 09 februari 2008 om 13:36 Permalink

  • Ivo Limmen schreef:

    Ik vraag mij af of het niet makkelijker is om je eigen object communicatie protocol te schrijven door gebruik te maken van .NET Remoting. In remoting kan je je eigen Sink schrijven die de vertaling doet (bijna hetzelfde als jou voorbeeld). Tevens zorgt remoting ervoor dat de applicatie gaat luisteren op geconfigureerd porten naar binnenkomende communicaties.

    Geplaatst op 06 februari 2008 om 22:09 Permalink