Uitrollen van Nieuwe Versies van Client/Server-Applicaties
De uitrol van een nieuwe versie van het cliëntgedeelte van een client/server-applicatie ging niet zo makkelijk als we dachten. De bestanden (executable en dll’s) van de cliëntapplicatie zijn dan namelijk (vaak) geblokkeerd. Tijdens de analyse van dit probleem en het zoeken naar een oplossing kwamen we meer problemen tegen, zoals een wijziging in de interface tussen de client en de server.
In dit artikel beschrijf ik een oplossing die het mogelijk maakt om een nieuwe versie van de client-applicatie uit te rollen, ook als deze in gebruik is. Daarnaast beschrijf ik de andere problemen die bij de analyse naar voren kwamen en probeer daarbij een oplossingsrichting aan te geven.
De invloed van de architectuur op de uitrol van nieuwe versies
Voordat je met de bouw van een applicatie gaat beginnen, moet je een architectuur kiezen. Er kunnen veel punten van invloed zijn op de keuze van de architectuur. Waar je waarschijnlijk vaak niet over nadenkt bij het kiezen van de architectuur, zijn de zaken die komen kijken bij het uitrollen van nieuwe versies van je applicatie. Dat probleem hebben wij in de praktijk ervaren.
Het uitrollen van nieuwe versies van stand-alone Windows-applicaties en webapplicaties levert over het algemeen niet veel problemen op. Bij een stand-alone Windows-applicatie is er slechts één gebruiker per werkstation. Die ene gebruiker kun je eenvoudig informeren dat je een nieuwe versie uitrolt. Een nadeel van deze architectuur is dat je nieuwe versies op alle werkstations moet installeren en meestal betekent dit dat men alle werkstations langs moet.
Bij de uitrol van een nieuwe versie van een webapplicatie speelt dit niet, omdat deze applicatie via een centrale webserver beschikbaar is. Wat daarbij wel kan spelen is dat de website of een gedeelte daarvan tijdelijk niet beschikbaar is.
Bij een client/server-applicatie hebben we hetzelfde voordeel als bij een webapplicatie, namelijk dat de applicaties centraal staan. Maar toch gaat de uitrol van een nieuwe versie niet zo makkelijk als bij een webapplicatie.
Onze applicatie
Bij onze applicatie hebben we om de volgende redenen voor een client/server architectuur gekozen:
- Via de applicatie moeten we gegevens in een database kunnen muteren (toevoegen, wijzigen en verwijderen);
- In de bestaande legacy-applicatie selecteren we gegevens of geven we aan welke gegevens we willen toe voegen. Met behulp van de nieuwe applicatie worden de gekozen mutaties daadwerkelijk uitgevoerd;
- Er zijn meerdere gebruikers tegelijk;
- Voor de toegang tot de database vanuit de nieuwe applicatie gebruiken we een driver waarvoor een licentie vereist is;
- Gebruikers van buiten onze organisatie gebruiken de applicatie en wij hebben geen toegang tot hun werkstations;
- De applicatie moet gebruikt kunnen worden vanaf thin clients.
De laatste drie punten zijn de reden dat we hebben gekozen voor een client/server-applicatie. Een webapplicatie had ook een oplossing kunnen zijn, maar vanuit onze legacy applicatie is het opstarten van een webapplicatie niet gemakkelijk.
Een aantal eisen ondersteunen dus onze keuze voor een client/server oplossing.
Invulling van de architectuur
Ter invulling van de architectuur kiezen we voor een Windows-service als server-applicatie. Als client-applicatie gaan we een Windows-applicatie bouwen die we via een file-server beschikbaar gaan stellen. De applicaties gaan we bouwen in C# met Visual Studio 2002. Voor de Windows-service definiëren we een interface, zodat we vanuit de client-applicatie weten welke methodes de service beschikbaar heeft.
Schematisch ziet onze omgeving er dan uit als in figuur 1.

Fig. 1: Client/Server schema
Uitrol van nieuwe versies
Bij de uitrol van een nieuwe versie van onze client/server-applicatie bleken er problemen te zijn. Hierbij speelt, dat de omgeving 7 x 24 uur beschikbaar moet zijn.
Bij het uitrollen van een nieuwe versie van de cliëntapplicatie bleek deze applicatie (executable en bijbehorende dll’s) geblokkeerd te zijn doordat er gebruikers aan het werk waren. Hoewel de gehele applicatie over het netwerk naar het werkstation van de gebruiker wordt gehaald, blijft de applicatie op de server geblokkeerd totdat de laatste gebruiker de applicatie afsluit.
Een speurtocht op het internet leverde een aanwijzing op voor een mogelijke oplossing, nl. de AppDomain-klasse
Tijdens een overleg, waarbij ook een consultant van Microsoft betrokken was, hebben we alle problemen en diverse oplossingsrichtingen besproken.
Eén van de oplossingen zou het gebruik van Visual Studio 2003 en het .NET framework 1.1 kunnen zijn. Met die versie is het mogelijk om een Windows-applicatie op een webserver te zetten in plaats van op een fileserver. Hier zou het locking probleem opgelost zijn. Het overstappen op het .NET framework 1.1 was echter geen oplossing, omdat wij de externe organisaties niet konden dwingen over te stappen op versie 1.1 van het .NET framework.
Een daarvan afgeleide optie was te onderzoeken of de bedoelde functionaliteit met het .NET framework 1.0 na te bouwen is. Omdat dat een ongewis traject zou zijn, waarbij op voorhand door enkele afdelingen al enkele problemen werden genoemd, werd besloten om verder te zoeken naar een andere oplossing.
Voorlopig zaten we op een dood spoor. Er moest een nieuwe .Net applicatie gebouwd worden en de afdeling Operaties wilde een oplossing voor de problemen met het uitrollen, voordat de nieuwe versies van de applicatie in beheer genomen zou worden.
Een speurtocht op het internet leverde een aanwijzing op voor een mogelijke oplossing, namelijk de AppDomain-klasse.
AppDomain
De AppDomain klasse representeert het applicatiedomein, dat wil zeggen een geïsoleerde omgeving waarbinnen applicaties uitgevoerd worden. Bij de AppDomain-klasse kan je gebruik maken van de AppDomainSetup-klasse, die voor ons probleem belangrijk is. De AppDomainSetup-klasse bevat informatie voor de op te starten applicatie en kan aan de constructor van de AppDomain-klasse meegegeven worden. De AppDomainSetup-klasse heeft drie belangrijke attributen, namelijk:
- ShadowCopyFiles,
- ApplicationBase en
- ConfigurationFile.
Het ShadowCopyFiles-attribuut is de oplossing voor ons probleem. Door middel van dit attribuut kun je aangeven, dat de applicatie niet vanuit de directory waar de applicatie staat opgestart moet worden, maar dat er eerst een kopie van de applicatie gemaakt moet worden en dat deze kopie gestart moet worden. Hierdoor ontstaan er geen locks op de ‘originele’ applicatie, waardoor de oplevering van een nieuwe versie van de applicatie mogelijk is, ook als de applicatie in gebruik is. Je moet daarbij opletten dat het ShadowCopyFiles-attribuut van het type string is, waarin de waarde “true” of “false” gezet moet worden.
Het ApplicationBase-attribuut bevat de directory van waaruit de applicatie opgestart wordt.
Het ConfigurationFile-attribuut geeft ten slotte aan welk configuratiebestand door de op te starten applicatie gebruikt moet worden.
Bij gebruik van de AppDomain-klasse is het mogelijk om commandline-parameters door te geven aan de op te starten applicatie. Deze oplossing werkt ook bij Visual Studio 2003 met het .Net Framework 1.1.
In listing 1 zien we een voorbeeld van de main-methode van de AppStarter-klasse. In dit voorbeeld worden eventuele commandline-parameters die opgegeven zijn bij het opstarten van de AppStarter-applicatie, doorgegeven aan de op te starten applicatie.
static int Main ( string[] args )
{
string AppPath = args[0];
string ExeName = args[1];
string AppName = Path.Combine(AppPath, ExeName);
string ConfigName = AppName + ".config";
string DomainName =
string.Format("AppLoader-{0}", ExeName);
AppDomainSetup AppSetup = new AppDomainSetup();
AppSetup.ApplicationBase = AppPath;
AppSetup.ShadowCopyFiles = "true";
AppSetup.ConfigurationFile = ConfigName;
// Create new ApplicationDomain
AppDomain NewDomain = AppDomain.CreateDomain(
DomainName, null, AppSetup);
// Choose method depending of number of parameters
if( args.Length > 2 )
{
string[] PassParams = new string[args.Length - 2];
Array.Copy(args, 2, PassParams, 0, args.Length - 2);
NewDomain.ExecuteAssembly(
AppName, null, PassParams);
}
else
{
NewDomain.ExecuteAssembly(AppName);
}
return 0;
}
Listing 1: De main-methode van een AppStarter-klasse
In de sources bij dit artikel zit deze AppStarter-klasse, evenals een HelloWorld-applicatie, die je kunt gebruiken als applicatie om op te starten. Als je de HelloWorld-applicatie opstart vanuit de AppLoader-applicatie met ShadowCopyFiles = “false” en je probeert HelloWorld.exe te verwijderen, dan kan dit niet. Doe je hetzelfde met ShadowCopyFiles = “true”, dan kan het wel.
Nieuwe versie van de server-applicatie
De AppDomain-klasse biedt een oplossing om bijna altijd een nieuwe versie van de client-applicatie uit te kunnen rollen. Voor de uitrol van een nieuwe versie van de server-applicatie biedt dit geen oplossing als de server-applicatie als service geïnstalleerd is.
Als de server-applicatie opgestart is, hebben we hetzelfde probleem als bij de client-applicatie, namelijk locks op de bestanden van deze applicatie. Dit betekent dat we voordat we een nieuwe versie uit kunnen rollen, de service eerst moeten stoppen, maar dat betekent dat de gebruikers van de client-applicatie op dat moment foutmeldingen krijgen als ze de service aanspreken. Een simpele oplossing hiervoor kan zijn om, als de communicatie mislukt, een melding aan de gebruiker te tonen om de actie over een tijdje nogmaals te proberen. Hierbij ziet de gebruiker geen onderscheid tussen het tijdelijk niet beschikbaar zijn van de service vanwege de uitrol van een nieuwe versie of het niet beschikbaar zijn vanwege een storing.
Dit is ook op te lossen door tijdens het uitrollen van een nieuwe versie ergens een signaalbestand neer te zetten dat aangeeft dat er een nieuwe versie uitgerold wordt. De client-applicatie kan dan controleren of het signaalbestand bestaat op het moment dat de communicatie met de service niet lukt en dan de juiste melding aan de gebruiker tonen.
Voor de uitrol van een nieuwe versie van de server-applicatie biedt dit geen oplossing
Een andere optie is om een applicatie te bouwen als façade voor de ‘echte’ service. De façadeapplicatie heeft een lijstje van poorten (minimaal 2) waarop instanties van de ‘echte’ service luisteren en zet de aanroep van de client door naar een poort waarop een ‘echte’ service luistert. Indien op een poort geen verbinding met een service verkregen kan worden, probeert de façadeapplicatie de volgende poort. Door eerst een nieuwe versie van de service op te starten die luistert naar een niet gebruikte poort, kunnen daarna de oude versies gestopt en vervangen worden zonder dat de gebruiker hier iets van merkt. Zie figuur 2 voor een schematische weergave. Bijkomend voordeel van deze methode is dat op de firewall slechts één poort opengezet hoeft te worden voor de externe communicatie.

Fig. 2: Schema van de omgeving
Interface
Bovenstaande oplossingen kunnen we alleen gebruiken als de inferface tussen de client-applicatie en de server-applicatie niet wijzigt. Wijzigt de interface wel, dan kan de oplevering alleen plaats vinden door de server-applicatie te stoppen. Er kunnen dan geen actieve gebruikers van de client-applicatie zijn en de nieuwe versies van de server-applicatie en de client-applicatie moeten tegelijktijdig uitgerold worden. Het wijzigen van de interface moet je dan ook zoveel mogelijk voorkomen.
Een mogelijke oplossing voor dit probleem zou gegevensoverdracht tussen de client-applicatie en de server-applicatie zijn op basis van XML. Op die manier hoef je niet voor ieder gegeven een aparte parameter aan een methode toe te voegen. Gelijktijdig wijzigen van de client-applicatie en de server-applicatie is dan alleen nog nodig als het gegeven echt aan beide kanten tegelijk noodzakelijk is.
Conclusie
Om het uitrollen van client-applicaties in een 7*24 uur omgeving mogelijk te maken biedt een zogenaamde AppLoader-applicatie die gebruik maakt van de AppDomain- en AppDomainSetup-klassen een goede mogelijkheid. Deze oplossing is eveneens te gebruiken voor stand-alone applicaties die vanuit een centrale beheeromgeving vervangen moeten worden door een nieuwe versie.
Voor de server-applicatie moet je eigenlijk gebruik gaan maken van een façade server, die op een reeks poorten zoekt naar een beschikbare server. Dit alles is bruikbaar zolang de interface niet wijzigt en om dit zoveel mogelijk te voorkomen zou je gegevens zoveel mogelijk in XML-formaat over moeten dragen en niet in aparte parameters. Als dit geen uitkomst biedt, dan zul je uiteindelijk de complete client/server omgeving in één keer moeten uitrollen en voor lief nemen dat deze applicatie tijdelijk niet beschikbaar is.
- Links: http://www.gotdotnet.com/team/clr/appdomainfaq.aspx
Sources
De sources die bij dit artikel horen kun je downloaden via belder_uitrollen_src.zip.