Integratie Patterns
Uitzonderlijk! Abnormaal! Niet van deze tijd! Dit zijn de kreten die van toepassing zijn als je nu nog applicaties ontwikkelt die nergens mee hoeven te integreren. Menig ontwikkelaar denkt nog wel eens terug aan de dagen waarin een applicatie gewoon een executable was die je kon kopiëren naar een diskette, dubbelklik, starten en draaien. Geen afhankelijkheden, geen problemen, geen integratie. Die tijd is voorbij. Nagenoeg elke applicatie die we vandaag de dag ontwikkelen, moet op enige manier integreren met andere systemen. Dit kan variëren van een eenvoudige integratie met de mailserver tot een complexe connectie naar een legacy mainframe toepassing.
Gelukkig voor .NET ontwikkelaars biedt het .NET Framework legio hulpmiddelen bij het integreren met externe systemen. Voor mail hebben we de System.Web.Mail namespace, voor het koppelen aan andere systemen en platformen is er een enorme support voor web services. Da’s mooi, maar hoe kunnen daar in onze code nu handig op inspelen, op dat integreren met andere toepassingen? Uiteraard zijn er al vele oplossingen bedacht en gelukkig zijn er ook een paar design patterns die bijzonder handig zijn bij het oplossen van onze integratievraagstukken.
In dit artikel zal ik het Proxy design pattern en het Broker design pattern onder de loep nemen. We nemen als voorbeeldapplicatie een website waar we vliegreizen kunnen boeken. Onze applicatie bestaat uit meerdere pagina’s: eerst moet de gebruiker zijn reisdata, bestemming e.d. invoeren en vervolgens zal de gebruiker de beschikbare vluchten met prijzen willen zien. Alle vluchtinformatie zit in een mainframe-applicatie. Onze webapplicatie is gemaakt met ASP.NET en zal op enig moment een aanroep moeten doen naar het mainframe om de beschikbare ticketinformatie op te halen. Het mainframe is niet ontwikkeld om duizenden gebruikers tegelijk sessies te laten openen. Het aantal accounts is schaars, dus hier moeten we handig mee omgaan.
Proxy design pattern
Het Proxy design pattern beschrijft een oplossing voor het instantiëren van objecten die ‘duur’ zijn om te instantiëren, bijvoorbeeld omdat het lang duurt om het object te laden, of omdat er resources aan het object zijn gekoppeld die niet langdurig geclaimd mogen worden, zoals in ons voorbeeld de connectie naar het mainframe.
Een ander voorbeeld waar we proxy gedrag kunnen zien, is Microsoft Word. Als je een document opent met heel veel plaatjes, dan worden niet automatisch alle plaatjes geladen. Alleen de plaatjes die direct zichtbaar zijn worden direct geladen. De overige afbeeldingen worden pas geladen op het moment dat ze nodig zijn, bijvoorbeeld als je omlaag scrollt of als je het document print. Als je snel scrollt, dan zie je nog even een vierkant met een kruisje erin.
Onze web applicatie heeft veel te doen met reserveringen. Een deel van de basisgegevens zoals vluchten en reserveringen, wordt in een SQL database opgeslagen, maar de daadwerkelijk stoelinformatie is duur om op te halen.
Listing 1a laat zien hoe we de stoelinformatie alleen ophalen als we die ook echt nodig hebben.
public class FlightReservation
{
private Seat _bookedSeat;
public string TravelerID;
public string FlightNumber;
public string DepartureCity;
public string DestinationCity;
public DateTime DepartureDate;
public DateTime ReturnDate;
///
/// SeatNumber of the actually booked seat for
/// this reservation.
///
public string BookedSeat
{
get
{
if ( _bookedSeat == null )
{
Mainframe m = new Mainframe();
m.Connect();
_bookedSeat = m.GetReservation(
this.FlightNumber, this.TravelerID );
m.Disconnect();
}
return _bookedSeat.SeatNumber;
}
}
}
Listing 1a: Ophalen van stoelinformatie
Stel je nu eens voor dat het ‘Seat’-object qua geheugen een enorm duur object is, b.v. omdat er in dit object een Image-property zit die laat zien waar in het vliegtuig zich de stoel bevindt. We willen dan dus eigenlijk het object zo snel mogelijk opruimen.
We passen de code even aan:
///
/// SeatNumber of the actually booked seat for
/// this reservation.
///
public string BookedSeat
{
get
{
try
{
if ( _bookedSeat == null )
{
Mainframe m = new Mainframe();
m.Connect();
_bookedSeat = m.GetReservation(
this.FlightNumber, this.TravelerID );
m.Disconnect();
}
return _bookedSeat.SeatNumber;
}
catch ( Exception exception )
{
// TODO: log the exception
throw;
}
finally
{
_bookedSeat = null;
}
}
}
Listing 1b: Ophalen van stoelinformatie, aangepaste versie
Er zijn vier vormen van het Proxy design pattern:
- Remote Proxy
- Virtual Proxy
- Protection Proxy
- Smart Reference
Listing 1b is een voorbeeld van een Remote Proxy gecombineerd met een Virtual Proxy. De Gang of Four (zie onder) beschrijven de Remote Proxy als volgt:
“A Remote Proxy provides a local representative for an object in a different address space.[GOF208]. This proxy is used to send requests to remote objects and any data the remote object needs.”
Met andere woorden: de aanroepende partij ziet niet dat voor een bepaalde property een remoting call wordt gedaan, b.v. een webservice wordt aangeroepen of een ander soort externe connectie wordt gemaakt.
Naar het Virtual Proxy design pattern wordt soms ook wel verwezen als het Lazy Evaluation pattern.
De GOF zegt over Virtual proxies:
“Virtual Proxy objects create expensive objects on demand [GOF208]”.
Met andere woorden: een virtuele proxy maakt objecten alleen aan als deze expliciet aangeroepen worden. Naar het Virtual Proxy design pattern wordt soms ook wel verwezen als het Lazy Evaluation Pattern.
Een Protection Proxy wordt toegepast om de toegang tot een object alleen toe te staan na controle van enige vorm van toegangsrechten:
“A protection proxy controls access to the original object.[GOF208]”)
Een Smart Reference is een vervanging van een reguliere pointer door een pointer die aanvullende acties verricht op het moment dat hij aangeroepen wordt. Dit komt meer uit de C++ tijd en komen we in .NET met managed code eigenlijk nauwelijks meer tegen. Je zou wellicht kunnen stellen dat de manier waarop .NET met object references omgaat een vorm van smart references is.
“A smart reference is a replacement for a bare pointer that performs additional actions when an object is referenced.[GOF209]”.
Broker design pattern
Figuur 1 laat zien hoe een stuk (client)code afhankelijk is van het interface van het object dat wordt aangeroepen: de client <> de ServiceInterface.

Fig. 1: Code-afhankelijkheid zonder remoting
De server implementeert het interface. In ons eerdere voorbeeld zou de interface van de class ‘Mainframe’ er uit zien als in listing 2:
public interface IMainframeServices
{
void Connect();
void Disconnect();
Seat GetReservation(
string flightNumber, string travelerID );
}
Listing 2
In listing 1a zien we dat we een Proxy instantiëren om met het mainframe te communiceren. We creëren het object, connecten aan de server, voeren een remote procedure call uit en sluiten de connectie. We moeten het plaatje nu iets complexer maken.
De client maakt nog steeds gebruik van het interface, maar instantieert niet rechtstreeks de server
De client maakt nog steeds gebruik van het interface, maar instantieert niet rechtstreeks de server maar in plaats daarvan een proxy object. Dit proxy object implementeert hetzelfde interface als de server, maar i.p.v. een implementatie van de logica die hoort bij de methode, implementeert de proxy ‘doorlus-gedrag’. M.a.w. de parameters worden middels een remoting call doorgestuurd naar de server en daar vindt de daadwerkelijke processing plaats. Aan de server kant zal iets van een server proxy draaien die ervoor zorgt dat de binnengekomen remoting call ook ergens terecht komt. In het geval van .NET Remoting gebeurt dit door de remoting configuration.

Fig. 2: Code afhankelijkheid met remoting
Maar wat als we niet weten welke server we moeten hebben? Of als we meer dan 1 server hebben en we willen een stuk load balancing implementeren. Dan wordt het noodzakelijk om een broker te implementeren: zie figuur 3.

Fig. 3: De ClientProxy gebruikt een broker om servers te vinden
De client-proxy heeft nu niet een ‘harde’ verwijzing naar de locatie van de server-proxy, maar vraagt aan het broker object om de locatie van een server aan te geven. Op deze manier kunnen servers zich aanmelden op het moment dat ze in de lucht komen, en afmelden op het moment dat ze down gaan. Het mag duidelijk zijn dat zowel client als server nu moeten weten waar de broker-service beschikbaar is. Dit zou kunnen door het broker-object te deployen naar zowel alle clients als alle servers en dit broker-object in een eigen database een lijst te laten bijhouden van alle beschikbare servers. Een alternatief zou zijn om de broker diensten als een web service op het netwerk te laten draaien. Dit laatste komt dan al heel dicht in de buurt van een UDDI-server.
Voor- en nadelen van het Broker pattern
Wat zijn de voordelen van het toepassen van het Broker design pattern?
Isolatie
Het scheiden van alle communicatie gerelateerde code in een eigen layer haalt de kennis uit je applicatie. Je kunt besluiten om de applicatie lokaal of gedistribueerd te draaien door de settings van de broker aan te passen.
Dit komt in het bijzonder tot uiting als je ook het Layered Application pattern toepast (zie een eerder magazine). Je kunt dan een afspraak maken dat code in de userinterface layer nooit zomaar code van de business layer mag aanroepen. Dit moet altijd via een daarvoor in het leven geroepen object broker. Een vereenvoudigde versie kan er zo uit zien:
public class ObjectBroker
{
object GetObject( string typeName )
{
// local is set from configFile
if ( local == true )
{
string assemblyFile = GetAssemblyForType(
typeName );
return Activator.CreateInstanceFrom(
assemblyFile, typeName ).Unwrap();
}
else
{
Type t = GetTypeForName( typeName );
string url =
ConfigurationSettings.AppSettings.Get("Url");
return Activator.GetObject( type, url );
}
}
}
Eenvoud
Het isoleren van de complexiteit rondom layers in één enkele oplossing maakt dat dit code niet gaat vervuilen en dat binnen een team van ontwikkelaars niet iedereen bezig hoeft te zijn met iets wat in de basis (best) lastig is.
Door dit pattern toe te passen kan de implementatie veranderen of runtime gestuurd worden door configuratie
Flexibiliteit
Dit is - denk ik - het belangrijkste. Door dit pattern toe te passen kan de implementatie veranderen of runtime gestuurd worden door configuratie. Een switch van .NET remoting naar DCOM raakt alleen maar de broker code, maar de applicatie blijft hetzelfde.
En wat zijn de nadelen van het Broker pattern?
Performance
Het gebruik van een object broker gaat ten koste van een stukje performance. Dit kun je beperken met caching, maar desalniettemin lever je ergens een beetje in. Daarbij moet wel opgemerkt worden dat het verlies uitgedrukt in procenten ten opzicht van de remoting call in het niets valt.
Conclusie
Het Proxy design pattern en het Broker design pattern zijn niet alleen waardevolle patterns als het gaat om het ontwerpen van een zelfstandige applicatie, ze komen ook bijzonder goed van pas bij het oplossen van integratievraagstukken. We hebben gezien hoe het Proxy pattern kostbare resources kan besparen of remoting kan abstraheren. Het Broker pattern kan load balancing en de vorm van remoting en distributie inkapselen zonder de applicatie als geheel onnodig complex te maken.
Links