Databases in een OO-paradigma


In de tig+ jaar dat ik nu software schrijf, ontwikkelde ik een grote voorliefde voor het zogenaamde object-georiënteerde programmeren. En gelukkig is dit ook de dominerende trend onder software-architecten. Zeker met de beweging weg van het client-server-model naar het webbased-model heeft de tegenhanger, database-georienteerd programmeren, danig aan populariteit verloren. Begrijp mij niet verkeerd. Ik zie het nut van een database. Maar voor mij is de database onderdeel van de architectuur en niet het uitgangspunt.

Historisch gezien hebben databases altijd een dubbelfunctie gehad: gegevensbeheer en het bieden van allerhande functionaliteiten. Bij functionaliteit denk ik aan stored procedures, views, functions, triggers en (zelfs) default values. In een object-georiënteerde architectuur horen dit soort zaken niet in de database thuis. Als voorbeeld zal ik het meest controversiële punten nemen: default waarden. Gegevens komen in een database terecht omdat een object wordt opgeslagen. Het object wordt geïnstantieerd (ver weg van de database) en dient bij het instantiëren de juiste default waardes te krijgen. Hier zit precies de crux. Functionaliteit dupliceren over zowel het objectmodel als de database leidt tot discrepanties in de implementatie. De default waardes voor eigenschappen van een class kunnen anders zijn dan de default waardes van een corresponderend record in de database. Dat wil je niet, en des harder je een keuze maakt tussen OO en DB, des te makkelijker je het jezelf maakt.

Een database heeft voor mij de volgende doelen: data-opslag, controle op data-integriteit en performanceverbetering. Het eerste doel is gekscherend te illustreren. Wat gebeurd er met een in memory object graph als de stroom uitvalt? Het geheugen is kwetsbaar, dus wil je gegevens op een harde schijf bewaren. Data-integriteit is een allerlaatste controle of de data die in een applicatie beheerd wordt klopt. Zijn de gegevens en hun onderlinge relaties in overeenstemming met de opgestelde integriteits-regels? Tot slot hebben databases rauwe verwerkingscapaciteiten. Databases zitten simpelweg dichter tegen het ‘metaal’ en hebben minder/geen netwerkverkeer nodig bij het doen van bevragingen en bewerkingen.

Als OO-man heb ik voor mijzelf de volgende uitgangspunten op een rijtje gezet. Het zijn richtlijnen over hoe met een database omgegaan moet worden. Deze gaan verder dan het fatsoenlijk normaliseren en denormaliseren. Richtlijn 1 is hierbij: alle (of op z’n minst zo veel mogelijk) functionaliteit en business logic  in de software onderbrengen, niet in de database.

Om de database als concept recht te doen volgt richtlijn 2: neem databases serieus binnen de functie die ze hebben. Als voorbeeld. Een database zonder indexen is een goudmijn voor performance-optimalisatie. Gebruik ze dus. Gebruik ook contraints voor primairy keys, foreign keys, unique values, en waar je maar even kan. Gebruik referentiele integriteit! En houd veldlengtes zo kort mogelijk. Zorg dat je een goed database-administrator hebt, die wil en kan functioneren binnen een OO-architectuur.

Databases hebben een interne structuur van tabellen, velden, constraints en indexen. Deze kun je middels een GUI met het handje aanpassen. Richtlijn 3: doe dit niet. Maak nooit handmatige aanpassingen in het database-schema. Gebruik een migrations framework waarin alle schema-verandering chronologisch zijn gescript. Als je wijzigingen in meerdere databases wilt doorvoeren dan zijn migrations onmisbaar. Denk hierbij alleen al aan een ontwikkel- en live-database.

Richtlijn 4: bepaal hoe je vanuit de software met de database gaat praten. Zoek je harde performance waarbij je als programmeur veel zelf kunt en moet doen? of ga je voor een echt object-georienteerde aanpak? En in welke mate lijken de classes/properties op de tabellen/velden. In het eerste geval kies je voor het simpelere Active Record, anders voor een full-blown Object-Relational-Mapper (ORM). Oftewel: ADO-recordsets, Linq2Sql of Entity Framework (om binnen de officiele Microsoft productcatalogus te blijven). Wie nu nog wil klooien met recordsets, moet zich achter de oren krabben. Deze optie valt heel snel af. Linq2Sql en EF hebben verschillen, maar over het algemeen gezien worden de mogelijkheden van Linq2Sql gezien als een subset van die van EF. EF is daarmee echt de grote broer van.

Toen ik leerde om met databases om te gaan begon de oefening als volgt: CREATE TABLE tblPersonen… Dit is een fout dit ik nu niet meer zou maken. Richtlijn 5 gaat over naamgeving. Allereerst is het de vraag of naamgeving in programmatuur in het Engels moet of in het Nederlands. In Duitsland kan ik mij de nationalistische discussie voorstellen, in Nederland niet. Ik kies zonder enige twijfel voor het Engels. Wat je ook doet: volg altijd de conventies die gebruikt worden in het software. Is de naamgeving in de software in het Engels, dan is de naamgeving in de database dat ook.

De software is leading, de database volgt. Weinig discussie mogelijk. Dit geldt niet alleen voor de taal, maar eigenlijk voor alles. Ik ga uit van de normale conventies die gelden voor C# (volgens de Framework Design Guidelines). Naamgeving van tabellen en velden vloeit  voort uit die van classes en properties. Dus hou ik de volgende conventies aan:

  • gebruik geen prefixen
  • veldnamen zijn nooit dezelfde als de tabelnaam
  • gebruik dezelfde namen voor velden die de zelfde soort gegevens opslaan. Voorbeelden: Order, CodeName, Name, Description, CreateDate. Denk hierbij aan interfaces als: IOrderable, INamed, enzovoort.
  • kies tabelnamen die niet te veel op elkaar lijken. Bij de twee tabellen ProductType and ProductsType kun je er op wachten dat iemand een fout maakt.
  • doe alsof de database case-sensitive is
  • stel conventies op en houd iedereen hier strikt aan (net als in de software)

Tot slot zijn tabelnamen in het meervoud. Product wordt dus Products, met een s. Probeer Meervoud-enkelvoud combinatie te maken die alleen op de uitgangs-s verschillen. Dus niet People-Person, maar Persons-Person. Dit vereist soms wat creativiteit, maar het helpt later. Puur voor het gemak.

De vorige richtlijn gaat vlekkeloos over in richtlijn nummer 6: koppeltabellen bestaan niet. Deze richtlijn is niet geheel onomstreden, maar ik ben er desondanks groot fan van. Koppeltabellen worden gebruikt om relaties tussen records die een n-op-m-cardinaliteit hebben op te slaan. In zo’n geval wordt een nieuwe tabel geintroduceerd die slechts twee foreign keys bevat, naar de gelieerde tabellen. Een voorbeeld met twee tabellen: Persons en Groups. Hoe gaat de koppeltabel heten? Er is een conventies die zegt dat je de namen van de tabellen achter elkaar zet in alfabetische volgorde: GroupsPersons dus. Trouwens, dit is een koppeltabel met twee tabellen. Een koppeltabel kan ook vier tabellen koppelen. Hoe heet de tabel dan? Afgezien van de naamgeving is er een ernstiger probleem. Wat gebeurt er als die koppeltabel een extra veld krijgt? Wat is dan het verschil tussen een ‘normale’ tabel en koppeltabel. Daarom zeg ik: koppeltabellen bestaan niet. Beschouw een koppeltabel als een normale tabel en kies een naam die past. Bijvoorbeeld: GroupMemberships.

Alweer geheel vloeiend komen we bij richtlijn 7: gebruik geen samengesteld primairy keys. Alle tabellen hebben precies 1 Primairy Key field. Aangezien we juist koppeltabellen verbannen hebben (die per definitie samengestelde primaire sleutels hebben) is deze afspraak makkelijk te houden. Gebruik voorts unique-constraints voor data-integriteit. De veldnaam voor de Primairy Key is altijd Id, wat afgeleidt is van de property Id op de class. Handig als je classes met een Id de interface IIdentifyable (met 1 property..) laat implementeren. Kies als datatype altijd voor de GUID boven een auto-increment int (meer smaken zijn er overigens niet echt). Het belangrijkste voordeel van de GUID is dat je van een nieuw object de Id-property zet in de software, en niet in de database. Principeel doe je dit omdat het genereren van id’s functionaliteit is. Praktisch wil je wel eens een sleutel hebben voordat het object is opgeslagen, en niet pas nadat het object in de database zit.

Abrupt tijd voor een geheel ander onderwerp: meertaligheid. Amerikanen hebben het in hun gelikte demos altijd makkelijk. Een veldje Name, een veldje Description en klaar. Wij Nederlands hebben snel te maken met vertalingen. Het is mij opgevallen dat juist in Nederland ontwikkelde software het best is voorbereid op het in kunnen voeren van meerdere vertalingen. Waar een klein landje goed in kan zijn. Maar goed, I digress. Richtlijn 8 heeft betrekking op vertalingen van teksten. De velden Name en Description lijken het zelfde, maar er is een cruciaal verschil dat verder gaat dan veldlengte. De naam van iemand of van een object is in alle talen het zelfde. Mijn naam is ‘Florian’, in het Nederlands, Engels, Duits en zelfs in het Spaans. Mijn omschrijving (of bio) is per taal anders. Dat wil zeggen: de inhoud is het zelfde, maar de tekst is anders. Name is 1 veld; Description is meerdere velden. Goed genormaliseerd stop je vertalingen in twee tabellen: TextEntries en TextTranslations. Het veld Description wordt een foreign key naar de eerstegenoemde tabel waarvoor meerdere vertalingen worden opgeslagen in de laatstgenoemde tabel. TextTranslations hebben twee velden: LCID en Translation. De LCID is een algeheel gebruikte afkorting voor landen en culturen. Voorbeelden zijn: nl-nl, nl-be, en-us, en-gb en fr-fr. Houd deze afkortingen aan, ook als je slechts de het eerste deel (de locale) gebruikt.

Richtlijn 9 gaat ook deels over locaties, maar dan tijdzones. Gebruiker, software en database werken niet op één computer. De website-bezoeker zit in Australie, gebruikt een webserver in Singapore die in verbinding staat met een database op Schiphol. Een extreem voorbeeld, maar ik wil hiermee aangeven dat de vraag: ‘hoe laat is het?’ zo maar drie antwoorden op kan leveren. Daarom is het goed om de ‘tijd’ van één computer/server af te halen. De database-server is hiervoor geen gekke optie. Houd er ook rekening mee dat de database-server fysiek kan verhuizen naar een andere tijdzone. Sla daarom datums op in een tijdzone-onafhankelijk format, het UTC-formaat.

Tot slot (om netjes, doch bij stom toeval, op het ronde aantal van 10 richtlijnen te komen) twee losse tips. Soms zijn properties van de classes die je in een database op wilt slaan zo divers en onvoorspelbaar, dat er op voorhand geen mapping van de maken is. Als een NO-Sql-oplossing  overkill is, sla objecten dan geserialiseerd in de database op. Doe dit niet binair, maar kies voor een XML-formaat. Dit maakt het debuggen, en het maken van wijzigen velen malen eenvoudiger. Kies voor ontwikkelgemak als er geen zwaarwegende performance-argumenten zijn. Kies er om dezelfde reden voor om geen documenten en afbeeldingen in de database op te slaan. Hou deze op de harde schijf en sla in de database alleen het pad naar de bestanden. Deze tip geldt zeker waarneer je werkt met een web-applicatie.

Tips en inzichten zijn welkom.

2 thoughts on “Databases in een OO-paradigma

  1. Mooie tips, alleen kan ik me nog niet zo vinden in het gebruik van GUIDs, maar misschien moet ik de nadelen van een Database toegekend ID nog tegenkomen 🙂

    • Hoi Ben,

      Dank je voor je reactie. Altijd leuk om constructieve feedback te krijgen. 🙂

      Een voorbeeld: Wij kunnen in onze software aan ieder willekeurig object een opmerkingen ‘hangen’. Deze opmerkingen worden opgeslagen in een tabel met een ObjectTypeName, een ObjectID en Comment (en meer velden, maar dit geeft een idee). Om een comment op te slaan, moet eerst het object zelf opgeslagen worden, omdat deze pas in de database een ID krijgt. Er zijn dus twee opslaan-acties nodig. En dat maakt het onmogelijk om goed te werken met database-transacties. Nou goed… Ik ga niet beweren dat onze oplossing (met een ObjectTypeName en een ObjectID) de beste is, maar het geeft wel aan dat het gebruik van auto-increment id’s tot een probleem kunnen leiden.

      Architectonisch wordt de ID bepaald door de software, en zou performance de reden zijn om dit in de database te doen. Ik kies architectuur voor snelheid (make it work, make it right, make it fast).

Leave a comment