WCF

Windows Communication Foundation er den delen av .NET rammeverket som er dedikert til kommunikasjon. Det betyr at det er viktig teknologi. Så jeg bør kanskje skrive noe om det, og i så fall kan du lese det her..

Bruk og testing av SOA komponenten

Dette er en oppfølgingspost til artikkelen hvor jeg beskrev hvordan jeg lager små SOA komponenter. Nå viser jeg hvordan jeg tar kompionenten i bruk, og måler hvor bra den virker.

Jeg oppretter et nytt Console application prosjekt, og legger til en Service Reference hvor jeg bruker adressen definert i BrainSpill tjenesten: http://localhost:8000/BrainSpill/service/mex. Dette genererer en MessageRepositoryClient klasse med metodene jeg tidligere definerte i IMessageRepository i BrainSpill-prosjektet. Vil jeg nå for eksempel sende en ny melding til BrainSpill kan jeg gjøre det slik som dette:

using (var client = new MessageRepositoryClient())

{

    client.AddMessage(new AddMessageRequest

    {

        Text = “Dette er en melding!”,

        By = “T-Man”,

    });

}

For å se hvor bra tjenesten håndterer mange requester forsøker jeg å opprette 100 meldinger om gangen i en loop, og måler hvor lang tid det tar:

static void Main(string[] args)

{

    using (var client = new MessageRepositoryClient())

    {

        System.Console.WriteLine(“{0} messages in database”, client.GetTotalNumberOfMessages());

 

        var stopwatch = new Stopwatch();

        stopwatch.Start();

 

        for (int i = 0; i < 100; i++)

        {

            client.AddMessage(new AddMessageRequest

            {

                Text = “Testmelding nummer “ + i,

                By = “T-Man”,

            });

        }

        stopwatch.Stop();

        System.Console.WriteLine(“Elapsed time: {0}”, stopwatch.ElapsedMilliseconds);

    }

}

Resultatet:

0 messages in database
Elapsed time: 587

Ikke så verst – i overkant av et halvt sekund for å sende og motta 100 requests og lagre dem i basen. For å se om performancen blir dårligere om det finnes mange meldinger i basen fra før kjører jeg testen en del ganger til. Men det ser ikke ut til å ha noen spesiell betydning:

10000 messages in database
Elapsed time: 514

Jeg bør også teste hvordan tjenesten oppfører seg med mange, samtidige brukere, og fyrer derfor opp seks command-winduer som hver kjører testprogrammet 10 ganger etterhverandre. Det vil altså si at jeg har seks ulike klienter som forsøker å hamre inn meldinger på en gang, samtidig som de hele tiden spør tjenesten hvor mange meldinger som finnes.

Resultatet varierte en god del. På det minste tok det nå 1,6 sekunder å sende 100 meldinger, på det meste helt opp i 16 – de fleste brukte et sted mellom 4 og 10 sekunder.

Jeg må også teste metoden for å hente ut ferske meldinger: GetLatestMessages. Jeg skriver om programmet mitt til å hente ut de 10 siste melding.

static void Main(string[] args)

{

    using (var client = new MessageRepositoryClient())

    {

        System.Console.WriteLine(“{0} messages in database”, client.GetTotalNumberOfMessages());

 

        var stopwatch = new Stopwatch();

        stopwatch.Start();

        var latestMessages = client.GetLatestMessages(10);

        stopwatch.Stop();

        System.Console.WriteLine(“Elapsed time: {0}”, stopwatch.ElapsedMilliseconds);

    }

}

Første gang jeg kjører testen tar det hele 3,3 sekunder, men når jeg forsøker flere ganger tar det bare litt over 0,6 sekunder. Jeg antar at det er db4o-databasen som cacher noe. Venter jeg en stund og så forsøker igjen, tar det igjen i overkant av 3 sekunder første gang, deretter 0,6.

Jeg er interrestert i å se om cachingen blir påvirket av at det kommer inn nye meldinger. Dvs. om det alltid vil ta lang tid første gangen etter at en ny melding har blitt lagret. Jeg skriver derfor et program som henter de siste meldingene noen ganger, legger inn noen meldinger, og så henter nye meldinger igjen:

static void Main(string[] args)

{

    using (var client = new MessageRepositoryClient())

    {

        System.Console.WriteLine(“{0} messages in database”, client.GetTotalNumberOfMessages());

 

        var stopwatch = new Stopwatch();

 

        Get10Messages(client, stopwatch);

        Get10Messages(client, stopwatch);

        Get10Messages(client, stopwatch);

        Add100Messages(client);

        Get10Messages(client, stopwatch);

        Get10Messages(client, stopwatch);

        Get10Messages(client, stopwatch);

    }

}

 

private static void Get10Messages(MessageRepositoryClient client, Stopwatch stopwatch)

{

    stopwatch.Reset();

    stopwatch.Start();

    var latestMessages = client.GetLatestMessages(10);

    stopwatch.Stop();

    System.Console.WriteLine(“Getting 10 messages took {0} milliseconds”, stopwatch.ElapsedMilliseconds);

}

 

private static void Add100Messages(MessageRepositoryClient client)

{

    System.Console.WriteLine(“Adding 100 messages..”);

    for (int i = 0; i < 100; i++)

    {

        client.AddMessage(new AddMessageRequest

        {

            Text = “Hi there……..”,

            By = “BrainSpill.Console”,

        });

    }

}

Resultatet bekrefter at cachingen ikke blir påvirket av at det kommer inn nye meldinger:

18200 messages in database
Getting 10 messages took 3373 milliseconds
Getting 10 messages took 629 milliseconds
Getting 10 messages took 632 milliseconds
Adding 100 messages..
Getting 10 messages took 672 milliseconds
Getting 10 messages took 633 milliseconds
Getting 10 messages took 615 milliseconds

Knagger: , ,

En enkel SOA komponent

SOA, tjenesteorientert arkitektur, er et utvannet og mye missbrukt begrep. For meg dreier det seg først og fremst om å lage små, mer eller mindre uavhengige tjenester, som kan kombineres og sammarbeide for å lage større løsninger. Den siste tiden har jeg laget en rekke slike små tjenestekomponenter. Jeg bruker omtrent den samme fremgangsmåten hver gang, og fikk derfor lyst til å dele dette mønsteret her.

SOA Component

BrainSpill.CoreDet viktigste prinsippet mitt når jeg lager slike komponenter er enkelhet. Jeg lager en liten, no fuss komponent, som ikke vet noe om verden bortsett fra akkurat det tjenesten skal tilby. Den eier sine egne data, og er så generisk at den i prinsippet kan brukes til mange ting.

Du vil se eksempler på bruk av db4o, LINQ, Ninject og WCF i denne artikkelen. Eksempelet jeg skal vise er en komponent jeg har kalt BrainSpill – en tjeneste hvor man kan lagre og hente ut meldinger. Jeg har planer om å bruke BrainSpill til å lage en twitter-lignende tjeneste til bruk internt på jobben. Hvis du ser for deg en hjerne som flyter over av tanker og ideer som bare må deles med andre så skjønner du sikkert hvordan navnet på tjenesten oppstod.

Jeg begynner med å lage et interface hvor jeg definerer hva tjenesten skal tilby. Jeg velger å gjøre dette i et eget class library prosjekt jeg kaller for BrainSpill.Core. IMessageRepository skal ta imot nye meldinger, gi tilgang til å slette meldinger, hente ut de siste n meldingene, og fortelle hvor mange meldinger som totalt er lagret i jenesten. (Dette er første versjon, og interfacet vil nok vokse noe etterhvert.)

    7 [ServiceContract]

    8 public interface IMessageRepository

    9 {

   10     [OperationContract]

   11     void AddMessage(AddMessageRequest messageData);

   12 

   13     [OperationContract]

   14     void DeleteMessage(MessageId id);

   15 

   16     [OperationContract]

   17     IEnumerable<Message> GetLatestMessages(int numberOfMessages);

   18 

   19     [OperationContract]

   20     int GetTotalNumberOfMessages();

   21 }

Jeg dekorerer interfacet med ServiceContract og metodene med OperationContract, slik at det kan publiseres vha. Windows Comunication Framework (WCF).

Legg merke til at jeg bruker tre andre klasser her: Message er en komplett melding lagret i tjenesten. AddMessageRequest holder dataene som brukes til å opprette en ny melding; en klient av tjenesten har ikke lov til å opprette fullverdige meldinger selv – det er f.eks. tjenesten som gir meldingen en id, lagringstidspunkt m.m. Jeg har også valgt å lage en egen klasse for meldingens id: MessageId. Denne er i prinsippet kun en wrapper for en Guid. Alle disse klassene dekoreres med DataContract-atributtet, og properties som skal være tilgjengelig på klientsiden dekoreres med DataMember.

Når alt dette er på plass koder jeg en konkret implementasjon av IMessageRepository. For tiden bruker jeg objektdatabasen db4o for lagring – det er absolutt det enkleste. Jeg gir derfor OdbMessageRepository en konstruktør som tar inn en IObjectContainer (en db4o database).

   15 public class OdbMessageRepository : IMessageRepository, IDisposable

   16 {

   17     private IObjectContainer _database;

   18 

   19     public OdbMessageRepository(IObjectContainer database)

   20     {

   21         _database = database;

   22     }

Deretter implementerer jeg metodene definert i IMessageRepository. Takket være db4o er dette ganske enkelt. Jeg benytter en del LINQ, og jeg vet ikke om koden jeg har skrevet her er optimal (sansynligvis ikke), men eksperimentering har vist at det er godt nok for meg i denne omgang.

   24 public void AddMessage(AddMessageRequest messageData)

   25 {

   26     Message messageToAdd = new Message(messageData);

   27     _database.Store(messageToAdd);

   28 }

   29 

   30 public void DeleteMessage(MessageId id)

   31 {

   32     _database.Delete(GetById(id));

   33 }

   34 

   35 public IEnumerable<Message> GetLatestMessages(int numberOfMessages)

   36 {

   37     var query = from Message m in _database

   38                 orderby m.Created descending

   39                 select m;

   40 

   41     return query.Take(numberOfMessages);

   42 }

   43 

   44 public int GetTotalNumberOfMessages()

   45 {

   46     var query = from Message m in _database

   47                 select m;

   48 

   49     return query.Count();

   50 }

   51 

   52 private Message GetById(MessageId id)

   53 {

   54     var query = from Message m in _database

   55                 where m.Id == id

   56                 select m;

   57 

   58     return query.First();

   59 }

Du la kanskje merke til at jeg lot OdbMessageRepository arver fra IDisposable? Objektdatabasen er nemlig en ressurs som må frigjøres på riktig måte. Derfor implementerer jeg Dispose().

   61 #region IDisposable Members

   62 

   63 private bool _isDisposed;

   64 

   65 public void Dispose()

   66 {

   67     Dispose(true);

   68     GC.SuppressFinalize(this);

   69 }

   70 

   71 private void Dispose(bool disposing)

   72 {

   73     if (_isDisposed)

   74     {

   75         return;

   76     }

   77     _isDisposed = true;

   78 

   79     if (disposing)

   80     {

   81         if (_database != null)

   82         {

   83             _database.Dispose();

   84             _database = null;

   85         }

   86     }

   87 }

   88 

   89 #endregion

BrainSpill.ServiceNå er kjernefunksjonaliteten i BrainSpill ferdig implementert. Jeg legger så til et nytt prosjekt for selve windows tjenesten: BrainSpill.Service. Jeg oppretter BrainSpillService som arver fra System.ServiceProcess.ServiceBase.

Selv om det kanskje er overkill i et så lite prosjekt som dette har jeg lagt meg til vanen å alltid bruke dependency injection, og tjenesten oppretter derfor en IoC container i konstruktøren. Jeg bruker som regel Ninject, som tar inn en DependencyModule – mer om den om litt..

    9 public partial class BrainSpillService : ServiceBase

   10 {

   11     public const string SERVICE_NAME = “BrainSpillService”;

   12 

   13     private IKernel _IocContainer;

   14     public ServiceHost _serviceHost = null;

   15 

   16     public BrainSpillService()

   17     {

   18         _IocContainer = new StandardKernel(

   19             new BrainSpillServiceDependencyModule());

   20 

   21         ServiceName = SERVICE_NAME;

   22     }

   23 

   24     protected override void OnStart(string[] args)

   25     {

   26         if (_serviceHost != null)

   27         {

   28             _serviceHost.Close();

   29         }

   30         _serviceHost = new ServiceHost(

   31             _IocContainer.Get<IMessageRepository>());

   32         _serviceHost.Open();

   33     }

   34 

   35     protected override void OnStop()

   36     {

   37         if (_serviceHost != null)

   38         {

   39             _serviceHost.Close();

   40             _serviceHost = null;

   41         }

   42     }

   43 

   44     protected override void Dispose(bool disposing)

   45     {

   46         if (disposing)

   47         {

   48             if (_IocContainer != null)

   49             {

   50                 // note: disposing the kernal also

   51                 // disposes the repository if needed

   52                 _IocContainer.Dispose();

   53                 _IocContainer = null;

   54             }

   55         }

   56 

   57         base.Dispose(disposing);

   58     }

   59 }

Koden er nokså standard for en windows service. Jeg overstyrer OnStart, hvor jeg oppretter en ny WCF ServiceHost. Jeg bruker Ninject til å opprette denne for meg, og spesifiserer kun navnet på interfacet, nemlig IMessageRepository. I OnStop stenger jeg ned den samme hosten.

Jeg overstyrer også tjenestens Dispose-metode, hvor jeg passer på å frigjøre IoC containeren. Den vil da ta seg av å kalle Dispose på alle objekter den har opprettet, som i dette tilfellet er OdbMessageRepository (som igjen kaller Dispose på db4o databasen).

Som sagt brukte jeg en Dependency-modul da jeg opprettet IoC containeren – koden for den ser du nedenfor. Jeg arver fra Ninject’s StandardModule, og overstyrer Load, hvor jeg definerer avhengighetene jeg trenger. I dette tilfellet er jeg kun interessert i å få opprette en IMessageRepository. Ved å bruke Ninject’s fluent interface sier jeg at når jeg ber om en IMessageRepository så vil jeg ha en instans av OdbMessageRepository. Jeg spesifiserer også at Ninject skal bruke Sinleton-oppførsel, hvilket vil si at den alltid bruker én og samme instans av klassen.

Til slutt forteller jeg hvilken parameter Ninject skal sende inn i konstruktøren til OdbMessageRepository. Jeg sier at argumentet som heter “database”, av type IObjectContainer, skal hentes fra metoden OpenDatabase(). Og OpenDatabase bruker db4o’s factory til å åpne basen fra en fil (eller opprette den om den ikke finnes fra før) basert på en filsti definert i configfilen.

   10 public class BrainSpillServiceDependencyModule : StandardModule

   11 {

   12     public override void Load()

   13     {

   14         Bind<IMessageRepository>()

   15             .To<OdbMessageRepository>()

   16             .Using<SingletonBehavior>()

   17             .WithArgument<IObjectContainer>(“database”, OpenDatabase());

   18     }

   19 

   20     private static IObjectContainer OpenDatabase()

   21     {

   22         return Db4oFactory.OpenFile(ConfigurationManager.AppSettings["database"]);

   23     }

   24 }

Det gjenstår noen steg for å få WCF til å fungere orntlig; jeg må definere adressen og bindingen – dette gjør jeg i configfilen.

CropperCapture[31]

Det irriterer meg at jeg må spesifisere den konkrete implementasjonen av interfacet her – OdbMessageRepository – men foreløpig vet jeg ikke om jeg kan unngå dette.

Når servicen er ferdig kompilert og installert BrainSpill, kan jeg nå legge til en service-referanse i klientprosjekter ved å referere til http://localhost:8000/BrainSpill/service/mex.

En liten detalj jeg oppdaget første gang jeg lagde en service som dette er at jeg også er nødt til å spesifisere InstanceContextMode for WCF tjenesten. Dette er pga. måten jeg oppretter ServiceHosten på – ved å gi inn en instans (fra IoC containeren) i stedet for en type (ref. BrainSpillService, linje 30 og 31). Dette er kanskje ikke ideelt, men inntil videre gjør jeg det slik, ved å spesifisere InstanceContextModeOdbMessageRepository:

   10 ///

   11 /// InstanceContextMode = InstanceContextMode.Single is needed because of the way

   12 /// the service is created (with an IoC container).

   13 ///

   14 [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]

   15 public class OdbMessageRepository : IMessageRepository, IDisposable

Nå er det ikke så mye igjen.., men for at alt skal fungere må jeg implementere en enkel Installer-komponent for windows servicen. Den ser slik ut:

    8 [RunInstaller(true)]

    9 public partial class BrainSpillInstaller : Installer

   10 {

   11 

   12     private ServiceProcessInstaller _process;

   13     private ServiceInstaller _service;

   14 

   15     public BrainSpillInstaller()

   16     {

   17         _process = new ServiceProcessInstaller();

   18         _process.Account = ServiceAccount.LocalSystem;

   19 

   20         _service = new ServiceInstaller();

   21         _service.ServiceName = BrainSpillService.SERVICE_NAME;

   22 

   23         Installers.Add(_process);

   24         Installers.Add(_service);

   25     }

   26 }

Og så må jeg ha en static void Main() som fyrer igang hele “sulamitten”. Jeg plasserer den for seg selv i Program.cs.

   11 static void Main()

   12 {           

   13     ServiceBase.Run(new BrainSpillService());

   14 }

Nå er jeg klar til å kompilere, og servicen er ferdig. For å ta den i bruk må den installeres og startes, og jeg lager vanligvis batch-filer for dette, sånn at jeg enkelt kan både installere og starte, og stoppe og avinstallere. Merk at du kan få problemer med servicen om du ikke alltid gjør dette i riktig rekkefølge. Om du forsøker å oppdatere servicen uten å stoppe og avinstallere først, kan det hende du må restarte maskinen for å få fjernet servicen.

Install.bat:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\InstallUtil BrainSpill.Service.exe

Start.bat:

net start BrainSpillService

Stop.bat:

net stop BrainSpillService

Uninstall.bat:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\InstallUtil /u BrainSpill.Service.exe

Så slik lager jeg tjenester for tiden. Beskrivelsen ble kanskje litt lang, men prinsippet er veldig enkelt. Spesielt fører bruken av objektdatabase (db4o) til at det blir lite kode. Har du synspunkter på denne fremgangsmåten, eller spørsmål, er du velkommen til å legge igjen en kommentar.

Knagger: , , , , ,

Windows Communication Foundation forklart på tre minutter

Mens inspirasjonen etter MSDN Live fortsatt er på topp skal jeg raskt forklare hva Windows Communication Foundation er for noe.., og hva det vil bety for oss utviklere.

.NET versjon 3.0 består av fire påbygninger til .NET 2.0 rammeverket, og vil bli releaset sammen med Vista ca. november. .NET 3.0 vil også fungere fint på Windows XP og på 2003 server (det er nå på høy tid å oppgradere fra 2000). De fire påbygningene – hvor jeg vil forklare den siste nærmere – er:

  • Windows Cardspace
    Microsoft’s forslag til standardisering av online identitet. For mer informasjon om identitet, besøk Kim Cameron’s Identity Weblog.
  • Windows Workflow Foundation (WF)
    Arbeidsflytmotor innebygget i operativsystemet som i praksis vil gjøre at utviklere vil kode business logikk mere deklerativt enn tidligere, og mere adskilt fra øvrig kode.
  • Windows Presentation Foundation (WPF)
    En helt ny måte å designe/utvikle brukergrensesnitt på både for Windowsplattformen og for Web.
  • Windows Communication Foundation (WCF)
    En aldri så liten revolusjon i all koding av kommunikasjon mellom klient og tjeneste.
netfx

Bakgrunnen for WCF (tidligere kaldt Indigo) er at det å f.eks. lage en web service som støtter sikkerhet, reliable messaging og transaksjoner krever mange tusen linjer kode. Og ønsker du i tillegg at tjenesten skal kunne kommunisere over TCP, ja da må du kode en god del til.

WCF endrer dette til å bli ingen kodelinjer i det hele tatt, og ved bruk av kun konfigurasjonsfiler kan du sette opp om tjenesten skal kommunisere over SOAP, nyeste web service med WS-security, WS-reliability og WS-transactions, TCP, named pipe, MSMQ, med CORBA, en-veis eller to-veis eller noe helt annet.

Altså, når du i fremtiden skal lage en tjeneste så koder du IKKE en web service på gamlemåten. Det du gjør er rett og slett å dekorere metodene du ønsker å publisere med noen attributter – dette blir kontrakten. I konfigurasjonsfilen bestemmer du så hvilken binding du ønsker å benytte. Bindingen definerer hvilken protokoll, transportmetode (text, binært eller annet), sikkerhet osv. Og så er du ferdig.

På klientsiden så gjør du omtrent som du er vandt til fra ASMX web servicer – du genererer en klasse på bakgrunn av tjenesten.

Og så konfigurerer du opp binding på samme måte som på tjenestesiden. Dette er genialt – som utvikler kan du glemme alt som har med implementasjon av kommunikasjon, og du kan bytte protokoller, sikkerhet osv. on demand.

Jeg anbefaler at du nå sporensträks tar turen innom wcf.netfx3.com. Der kan du laste ned .NET 3.0 (anbefaler at du allerede har VS 2005, men ikke nødvendig) og masse kodeeksempler for WCF. Enjoy!


Torbjørn: La oss anta to ulike definisjoner av Template Method pattern - de to ytterpunkte...

Lars-Petter: Hei igjen. Siden du inviterer til å ta diskusjonen i bloggen, og har tatt deg t...

Torbjørn: Lars-Petter: Det er én måte å se det på. Det er absolutt fortsatt Template M...

Lars-Petter: Hei. Har du ikke i prinsippet her gått over fra Template Method (arv) til Strat...

Christian Abildsø: I alle fall i C#, så føles dette som kode som blir mer fleksibel men vanskelig...

Torbjørn: Hei Henrik, og takk for tilbudet. Ble oppmerksom på Rasberry Pi for under en uk...

Henrik Sandaker Palm: Ang. større hobby prosjekt. Du er som er en slik rakker på programmering har j...

Øivind Nilsen: Slutt å bruke mobilnummeret mitt som eksempel !...

Bjørn Einar Bjartnes: Jeg har også latt meg fascinere av Clojure, uten at jeg har kommet så veldig l...

Bjørn Einar Bjartnes: Sweet :) Jeg tror egentlig jeg liker det som det er, med musikk. Litt av utford...

 Hold deg oppdatert

Søk i bloggen

Ferske innlegg

  • Template Method del 4: Multippel arv
  • Template Method Intermesso
  • Template Method del 3: Bare funksjoner
  • Template Method del 2: På vei mot funksjonell programmering
  • Kategorier

  • .net ninja (37)
  • Bøker (17)
  • Diverse prosjekter (35)
  • DSL (10)
  • Erlang (10)
  • F# (5)
  • Hardware (1)
  • Jobb (77)
  • Julekalender (51)
  • kjempekjekt.com (23)
  • LISP/Clojure (33)
  • NNUG / community (60)
  • O/RM & databaser (10)
  • Off topic (116)
  • OO-design/clean code (30)
  • Podcasts (14)
  • Polyglot (77)
  • Ruby (27)
  • Silverlight / RIA (3)
  • Software/verktøy (20)
  • Softwareutvikling (20)
  • Testing / TDD (30)
  • the contiki strip (13)
  • User experience (3)
  • WCF (3)
  • Webutvikling (32)
  • WPF (9)
  • WTF (12)
  • Last ned Wallpaper

    Programmeringsbloggens tøffe skrivebordsbakgrunn med snippets fra ulike språk laster du ned her!

    Abonner via epost

    Om du vil kan du få alle nye blogposter tilsendt til din epost. Abonner nå, det er kjempeenkelt!

    Meta