Waarom ik haast geen enums meer gebruik

imageEnums, kleine lijstjes met waarden die helpen bij het Type-safe en leesbaar houden van je code in de gevallen dat je een keuze wilt afdwingen of controleren. Maar je kan er zo bar weinig mee. De methode waar ik de voorkeur aan geef? Het Descriptor Pattern.

We gebruiken ze allemaal, de enums. Een lijst met hard gecodeerde waarden. Maar buiten de Type-safety en het eventueel 'flaggable' maken (meerdere keuzes tegelijkertijd) is er niet bij. Zo niet bij het Descriptor Pattern. Sander Hoogendoorn heeft in juni 2005 een mooi artikel gepubliceerd in het .Net magazine waarin hij  vijf verschillende wijzen beschrijft hoe met een lijst met waarden om te gaan. Sanders artikel is een echte aanrader.

Het Descriptor Pattern passen wij in het Venum project veel toe. Zeker omdat de lijsten die we gebruiken vaak net wat meer informatie weer moeten geven. Om hier gestructureerd mee om te gaan hebben we een DescriptorBase class gedefinieerd. Die het lastige werk van het creëren van List<T>, ToString()  en instantiëren voor zijn rekening neemt.

Nu eerst een stuk basis. Een eenvoudige Descriptor is als volgt:

public class SampleDescriptor
{
  public static SampleDescriptor None = new SampleDescriptor("None");
  public static SampleDescriptor Yes  = new SampleDescriptor("Yes");
  public static SampleDescriptor No   = new SampleDescriptor("No");

  private string _value;

  private SampleDescriptor(string value){
    _value = value;
  }

  public override string ToString() {
    return _value;
  }
}

De static fields kunnen we direct gebruiken als waarden in onze IDE. De Type-safety is gegarandeerd. De descriptor heeft een private constructor, een andere instantie maken is dus niet mogelijk. De aanroep van deze lijst is eenvoudig, bijvoorbeeld:

var x = SampleDescriptor.None;
if (x == SampleDescriptor.Yes)
  Console.WriteLine("Yes, it is.");;
Console.WriteLine(x);

Om deze lijst bijvoorbeeld weer te geven in een DropDownBox, kunnen we de datasource gewoon richten op de Descriptor, maar we moeten dan wel een method hebben die de Descriptor omzet naar een lijst.

public List<SampleDescriptor> GetList()
{
  var result = new List<SampleDescriptor>();
  foreach (var e in this.GetType().GetFields())
    if (e.FieldType == this.GetType())
      result.Add((SampleDescriptor)e.GetValue(null))
  return result;
}

Een uitgewerkt voorbeeld en een bruikbare DescriptorBase class heb ik in deze zip geplaatst : DescriptorSample.zip . Bekijk deze en maak er gebruik van, je zal zien dat dit een werkelijk geweldige manier is om met lijsten te werken. In het voorbeeld maak  ik ook gebruik van een resource file voor de beschrijvingen, op deze manier is nu eenvoudig mogelijk om de lijst te gebruiken in een meertalige User Interface.

Waarom ik liever een Extension Method gebruik

imageEen ieder die werkt met het .Net Framework 3.0 of hoger werkt vast en zeker met de zogenaamde Extension Methods. Althans, dat dacht ik. Toch spreek ik regelmatig vakgenoten die er weinig of geen gebruik van maken. Hoe komt dit toch? Is het het aloude onbekend maakt onbemind of licht er meer aan ten grondslag?

Maar eerst, wat is een Extension Method? Vrij vertaald is het een uitbreidingsfunctie. Een functie die wordt toegevoegd aan de bestaande set van functies van een bepaald Type. Deze functie is statisch en kan te allen tijd worden aangeroepen. Een klein voorbeeld: het object van het Type String beschikt niet van nature over de mogelijkheid om te bepalen of de tekst die het bevat een geldig e-mailadres bevat. De volgende syntax zou tot niets leiden:

var s = "misschien een emailadres";
if (s.IsValidEmail()) continue;

 

Zodra ik echter de Extension Method IsValidEmail heb gedefinieerd, dan kan ik deze te pas en te onpas aanroepen. Nu hoor ik de lezer al denken: "leuk allemaal, maar we hadden toch al de gewone 'helper' methods die ik in mijn bibliotheek had staan?" Inderdaad, die heb je al, en ik pleit er voor om deze af te stoffen en desnoods zelfs te herschrijven naar Extension Methods. Waarom? Omdat het zo veel meer leesbare code oplevert!

Nu hoorde ik laatst een collega van mij opperen dat er ook nadelen zitten aan het gebruik van Extension Methods. De nadelen die ik later vond stonden gewoon vermeld in de msdn documentatie. En ja, deze zou je als nadelen kunnen betitelen, zelf zie ik ze als slechts een kleine beperking op het grote gemak dat deze functies ons bieden. Volgens de msdn documentatie is het beter om je eigen types te definiëren daar waar mogelijk, je loopt immers het risico dat het Type dat je uitbreidt door een andere partij wordt gewijzigd. Daarmee komt je Extension Method potentieel in de gevarenzone.

Deze waarschuwing uit de documentatie moeten we wel in de gaten houden, het is een terecht punt. Niettemin, denk aan de kracht die Visual Studio je nu kan bieden! Al die oude 'helper' methods worden nu via IntelliSense aangeboden (mits je uiteraard de namespace van de extensionmethod in je using directives hebt geplaatst). Geen uitingen meer van: 'welke functie had ik hier voor bedacht!'

Vandaag heb ik nog een Extension Method voor een simpele afvanging van is null gemaakt. Ik zat met het uitvragen van custom properties in een Xml node.

var s = node.GetProperty("SomePropertyName").Value;

 

Zodra de property niet gevonden zou worden, zou ik een Null-reference Exception om mijn oren krijgen. Bedenk nu dat ik vele van deze regels code moest bewerken. De volgende oplossing werkt dan wel, maar zou mijn code enorm 'vervuilen':

var s = node.GetProperty("SomePropertyName")!=null?
                node.GetProperty("SomePropertyName").Value:
                   String.Empty;

 

Liever maak ik snel een Extension method aldus:

public static string GetNullableValue(this Property value)
{
   if (value == null) return string.Empty;
   return value.Value;
}

 

Dit zorgt er voor dat ik nu elke regel als volgt kan toepassen:

var s = node.GetProperty("SomePropertyName").GetNullableValue();

 

Dus, waarom gebruik ik bijna overal een Extension Method:

  • leesbare code;
  • ondersteuning IntelliSense;
  • snelle opbouw van mijn codebibliotheek;
  • en het is zoooo makkelijk.

 

Onze ORM tooling keuze voor Venum

clip_image002 Er zijn veel ORM tools beschikbaar voor .Net waarvan de belangrijkste hieronder genoemd. 
- ADO.NET Entity Framework
- Linq to SQL- ADO .NET
- LLBL Gen
- NHibernate

ADO.Net Entity Framework

We hebben met ADO.NET Entity Framework geëxperimenteerd. Echter viel deze optie al vrij snel af. Want de versie die wij gebruikten (.NET 3.5, VS2008, SP1) kon classes uit een bestaande database genereren. Daarbij waren de objecten "vervuild" met specifieke Entity Framework attributen en overervingen. En laat dat nu net het tegenovergestelde zijn van Domain Driven Design. Voor Visual Studio 2010 is er een nieuwe versie beschikbaar die wel op basis classes een database kan genereren. Erg mooi, maar de database definiëren zit niet het meeste werk in, en de classes blijven 'vervuild' met Entity Framework objecten.

Linq to SQL

Linq is een techniek die we in het Venum project vaak toepassen om resultaten te krijgen uit collecties door middel van een query. Hierdoor lijkt Linq to SQL de perfecte keuze voor het Venum project. Echter zit er een groot nadeel in het gebruik van Linq to SQL, en wel het volgende: Linq to SQL vertaald classes 1 op 1 met tabellen. Hierdoor heb je niet de gehele vrijheid in het ontwikkelen van het Domein Model. Zo moeten alle properties bestaan in de class die in de corresponderende tabel zijn gedefinieerd. Ook is het tot nu toe voor ons onduidelijk hoe Linq to SQL om kan gaan met overervingen.

LLBL Gen

Wij zijn niet diep in de techniek van LLBL Gen gedoken omdat vrij snel duidelijk was dat het de code in het domein model "vervuild" met LLBL Gen specifieke code.

NHibernate

NHibernate is behoorlijk complex om te leren maar dat lijkt achteraf het enige nadeel van NHibernate. Want het voldoet aan alle eisen die wij stellen aan de ORM tooling voor het Venum project. Ten eerste hoeft het domein model niet "vervuild" te worden met NHibernate attributen of overervingen. Ten tweede geeft NHibernate de vrijheid de database naar keuze in te richten nadat het domein model is gedefinieerd en eventueel visa versa als men Data Driven Design wil toepassen. Wij maken gretig gebruik van deze vrijheid, met als voorbeeld dat we voor een abstract class 1 tabel definiëren met alle eigenschappen van de gerelateerde superclasses. Er zitten echter nog wel wat "addertjes onder het gras" maar die zullen worden beschreven in het volgende hoofdstuk.

Hoe wij ORM toepassen in Venum

In Venum hebben wij meerdere typen datalagen geïmplementeerd. Hieronder vallen de volgende implementaties:

- Mock: Voor het testen van de applicatie

- WCF: Voor het communiceren met de webservice(s).

- Database: Voor het schrijven en lezen uit een database.

De bovenstaande implementaties implementeren dezelfde interface hetgeen het mogelijk maakt een andere implementatie te injecteren in de testomgeving dan in de productieomgeving.

In dit hoofdstuk laten we de Mock en de WCF implementatie van de datalagen ongemoeid en zullen we enkel de database implementatie onder de loep nemen. Dit is immers de enige implementatie die gebruik maakt van ORM (NHibernate). Nergens anders in de applicatie zullen we ORM gerelateerde referenties tegenkomen.

Onze ORM regels

ORM-tools moet weten hoe instanties van objecten in de database opgeslagen worden. Hiervoor moeten 'mappings' worden gedefinieerd die dit beschrijven.

Er zijn een paar basis-regels die wij hanteren voor ORM binnen het Venum project.

Schoon domein model

We hebben gekozen om geen ORM gerelateerde code of attributen in het domeinmodel te verwerken. Hiermee verliezen we aan de ene kant opties om mapping simpel te maken, aan de andere kant winnen we op het gebied van flexibiliteit. Alle mapping gebeurd aan de hand van de xml bestanden in de database laag.

1 op 1 relaties

Alle 1 op 1 relaties worden in de database in dezelfde tabel opgeslagen, tenzij de relatie null kan zijn.

Stel je hebt class Person met property Address van het type GeoAddress:

public class Person
{
    private GeoAddress m_address = new GeoAddress();

    public string Name{ get; set; }

    public GeoAddress Address
    {
      get { return m_address; }
      set { m_address = value; }
    }

    public class GeoAddress
    {
      public string Street{ get; set; }
      public string City { get; set; }
    }
}

 

Dan wordt de tabel tblPerson als volgt gedefineerd:

 

tblPerson

Name

Street

City



Het voordeel van het opslaan van 1 op 1 relaties van het domeinmodel in de database tables is dat er geen join statements in de queries naar de database hoeven worden uitgevoerd wat de snelheid van data opslaan en uitlezen bevorderd. Let wel, de bovenstaande constructie gaat (helaas) niet op voor 1 op 1 relaties die null kunnen zijn.

Overervingen

Verschillende overervingen van subclasses worden in 1 tabel opgeslagen. Het exacte type van de instantie wordt door NHibernate achterhaald aan de hand van een zogenoemde discriminator waarde. Voor elke superclasse kan een discriminator waarde worden gedefineerd waarmee NHibernate kan achterhalen welke waarden moeten worden opgeslagen voor welk type. En nog belangrijker, welk type superclass NHibernate moet retourneren bij het uitlezen van een record uit de table.

Hieronder een voorbeeld van overervingen:

public class Boss: Person
{
  public DateTime DateOfBirth { get; set; }
}
public class Employee: Person
{
  public string CompanyName { get; set; }
}
public abstract class Person
{
  public string Name{ get; set; }
}

 

Dan wordt de tabel als volgt gedefineerd, waarbij de discriminator waarde in PersonTypeID wordt opgeslagen:

tblPerson

PersonTypeID

Name

DateOfBirth

CompanyName



Het voordeel van het opslaan van superclassen in een tabel met de naam van de subclass is dat er geen waarden dubbel hoeven worden beschreven voor NHibernate. Hierbij hoeft bijvoorbeeld maar 1 tabel worden uitgebreid als de class Person wordt aangevuld met extra properties.

Database validatie

Vrijwel alle validatie van instanties worden uitgevoerd door het domein en zo min mogelijk door de database, hoewel deze wel veel mogelijkheden daartoe biedt. Echter zou dat betekenen dat we validatie dubbel op aan het implementeren zijn.

Alle kolommen van de tables in de database mogen null-waardes bevatten, behalve de kolommen die als primaire sleutel zijn gedefinieerd.
Wel worden er foreign keys gedefinieerd maar niet met oogpunt van relationele integriteit, maar om NHibernate te helpen met beschrijven van relaties. De naam van de foreign keys kunnen namelijk gebruikt worden voor het beschrijven van relaties voor NHibernate.

Onderhoud van data

Het schrijven van SQL-queries voor onderhoud van de data raden wij af. Dit om twee redenen, namelijk:

1. Extra onderhoud van het product, de SQL queries zouden ook moeten worden bijgewerkt als er veranderingen optreden in het domeinmodel.

2. Er kan corrupte data ontstaan omdat men met behulp van SQL queries kolommen met foutieve waarden kan vullen die niet meer goed te mappen zijn met het domein model.

Om toch snel en effectief bijvoorbeeld onderhoud applicaties te kunnen schrijven maken we gebruik van het domeinmodel met het bijbehorende NHibernate project. Denk bijvoorbeeld aan het starten van een nieuw project die gebruik maakt van de bovengenoemde projecten. Hierbij wordt het relationele databeheer volledig aan NHibernate overgelaten en wordt het data beheer volledig geschreven in code aan de hand van instanties uit het domeinmodel die gepersisteerd moeten worden.

In het vervolg van dit artikel kunt u informatie verwachten met voorbeelden van NHibernate en over de architectuur van Venum.

Meerdere soorten Views in PRISM

image In deze post ga ik het hebben over een probleem waar ik tegen aan ben gelopen bij het gebruik van Prism. Deze techniek wordt toegepast in Silverlight -waarin wij werken- maar ook in WPF applicaties. Deze techniek zorgt er voor dat de verschillende delen van de applicatie losgekoppeld worden en daarmee makkelijk te veranderen of te hergebruiken zijn. Voor meer informatie over Prism ga naar http://compositewpf.codeplex.com/ of volg de goede tutorial van Channel9 hier: http://channel9.msdn.com/posts/akMSFT/Creating-a-modular-application-using-Prism-V2-Part-1-of-4--Creating-a-shell-and-modules/.

Het probleem
Wanneer ik in Prism verschillende Regions defineer en daar views in stop, kan ik niet de lay-out van mijn applicatie drastisch veranderen per scherm.  Neem bijvoorbeeld deze lay-out:

image

Wat als ik nou een loginscherm wil hebben waarbij bijvoorbeeld linksboven het login gedeelte zit en in het midden een news feed? Dan moeten mijn regions wel heel anders gedefinieerd zijn dan hierboven het geval is. Na een tijdje hiermee gestoeid te hebben ben ik tot de volgende oplossing gekomen.

De oplossing

Eerst heb ik een full screen region gemaakt in de shell.xaml. Dit is de enige region in de Shell.

<ContentControl regions:RegionManager.RegionName="FullScreen"
  VerticalContentAlignment="Stretch"
  HorizontalContentAlignment="Stretch">
</ContentControl>

 

Wanneer de applicatie opstart registreer ik de Welcome view met het fullscreen region:

m_regionManager.RegisterViewWithRegion(Regions.FullScreen.ToString(), () =>
   m_container.Resolve<ViewWelcome>());

 

De ViewWelcome heeft zijn eigen regions voor het login scherm, zoals ik boven beschreven heb.

Wanneer de gebruiker is ingelogd, deactiveer ik het Welcome scherm en activeer ik het Main scherm, deze bevat alle andere regions zoals boven in het plaatje te zien is. Ik doe dit in de Shell.xaml.cs codebehind, door me te subscriben aan een event door gebruik te maken van de eventaggregator zoals hier in de constructor:

m_regionManager.RegisterViewWithRegion(Regions.FullScreen.ToString(), () => 
  m_container.Resolve<ViewWelcome>());

 

En dit is de method die gebruikt wordt om tussen de views te wisselen:

private void switchViewsEventHandler(string viewSetName)
{
  switch (viewSetName.ToLower())
  {
    case "login":
      RegionViewManager.DeactivateView(         
         m_regionManager.Regions[Regions.FullScreen.ToString()],
           typeof(ViewMain));
      RegionViewManager.AddAndActivateView(
         m_regionManager.Regions[Regions.FullScreen.ToString()],
           typeof(ViewWelcome), m_container);
      break;
    case "main":
      RegionViewManager.DeactivateView(
         m_regionManager.Regions[Regions.FullScreen.ToString()],
           typeof(ViewWelcome));
      RegionViewManager.AddAndActivateView(
         m_regionManager.Regions[Regions.FullScreen.ToString()],
           typeof(ViewMain), m_container);
      break;
  }
}

 

Wat hier het interessante is, is waarschijnlijk de deactiveer en activeer methods in de RegionViewManager class, dus die laat ik hier zien.

public static class RegionViewManager
{
  public static void DeactivateView(IRegion region, Type viewType)
  {
    foreach (object view in region.Views)
    {
      if (view.GetType() == viewType)
      {
        region.Deactivate(view);
      }
    }
  }
  public static void AddAndActivateView(IRegion region, Type viewType, 
      IUnityContainer container)
  {
    bool found = false;
    foreach (var existingView in region.Views)
    {
      if(existingView.GetType() == viewType)
      {
        region.Activate(existingView);
        found = true;
        break;
      }
    }
    if (!found)
    {
      var view = container.Resolve(viewType);
      region.Add(view);
      region.Activate(view);
    }
  }
}

 

Om het samen te vatten: eerst activeer ik de Welcome view, die in de fullscreen region zit. Dan, wanneer ik inlog, deactiveer ik de Welcome view en activeer ik de main view, die ook in de Fullscreen region zit. Deze views hebben beide hun eigen regions. Dit maakt het mogelijk om zoveel verschillende layouts voor mijn applicatie te gebruiken als ik wil.

We zijn nog steeds bezig deze technieken te verbeteren. Als je tips hebt, twijfel dan niet te reageren.

Hij die opvoedt, niet hij die verwekt is de vader.

imageAl vele malen heb ik mijn vakgenoten hun werk, de geschreven code, hun 'kindjes' horen noemen. Het is dan ook niet vreemd dat velen van hen zich direct op de tenen voelen getrapt wanneer je iets van kritiek levert, hoe opbouwend dan ook. Buiten het niet kunnen velen dat er kritiek wordt geleverd gaat de analogie wellicht nog verder.

Als software architect bij Wazzup Inc wordt mij gevraagd nieuwe software systemen te ontwerpen maar ook te implementeren. Het mooie van het ontwerpproces is dat je prachtige constructies kan bedenken die al het gevraagde aan zal kunnen. Je wordt geconfronteerd met puzzels waarbij bestaande en nog te ontdekken technieken gebruikt kunnen worden. Dit onderdeel dat wellicht nog het meest te vergelijken valt met het flirten, daten en wat nog meer is een prachtige beleving.

Het realiseren van de verschillende plannen die bedacht zijn heeft meer weg van het opvoeden van het kind dat uiteindelijk de wijde wereld in zal gaan en op eigen benen moet kunnen staan -wellicht nog wat hulp hier en daar van de ouders, maar goed.

Met de beste intenties kan er helaas heel erg veel misgaan. Dat wat nu een goed plan lijkt, kan na verloop van tijd desastreuse effecten hebben. In de jaren 60 en 70 raadpleegden veel ouders de adviezen van dr. Spock, wij software ontwikkelaars hebben echter meer baat bij de design patterns van Booch, Gamma, Fowler en Martin.

Bij het opvoeden is het van belang om zo consequent mogelijk te zijn doch flexibel op gezette momenten. De lijnen moeten duidelijk zijn en het pad uitgezet. En toch, toch blijft het gevaar van al die zaken waar geen -echte- controle op te krijgen is. Vrienden, de anderen, waar gaat het kind mee praten. Zal het nog steeds zijn verworven identiteit behouden. Zal het nog kunnen functioneren?

Ik hoop de komende tijd genoeg opvoedkundige tips met jullie te kunnen delen en uiteraard genoeg op te pikken om de zwakke punten in mijn opvoeding te verbeteren.

Welkom

Hoi! Welkom op deze nieuwe blogsite van de 'de jongens van Wazzup'! We willen met deze site onze technische ervaringen en verkenningen delen met de rest van de wereld.

Wie we zijn? 'de jongens van Wazzup'!  De software ontwikkelaars van Wazzup Inc.

Waar we het meest mee werken? Microsoft technologie!