torsdag 26. februar 2009 Databaser db4o
Det finnes en utfordring nesten alle software-prosjekter lever med, og det er den konseptuelle forskjellen mellom objekt-orientering og relasjons-modellen. Koden vår struktureres etter objekt-orienterte prinsipper, og vi forsøker å skape en abstraksjon over vår logikk som best mulig modellerer det domenet/den verden vi skal representere. Men som regel benytter vi også en relasjonsdatabase - og disse to modellene er svært ulike. Vi sier at det eksisterer en impedance mismatch mellom objekt-orierntert kode og data i en relasjonsmodell.
Dette fører til unødvendig kompleksitet. Objektene våre må ofte struktureres på en bestem måte for å kunne persisteres fornuftig i databasen, og vi kan ikke fokusere så mye på domenemodellen som vi skulle ønsket. O/R-mappere som f.eks. NHibernate hjelper oss et stykke på veien, men de kan også føre med seg sine egne problemer, og har et performance overhead. Dessuten er det en ekstra teknologi man da må lære seg.
Men er vi virkelig nødt til å "skvise" objektene våre ned i relasjonsdatabaser? Svaret er NEI, DET ER VI IKKE NØDT TIL!
Første gangen jeg hørte om objektdatabaser (eller ODBMS som man gjerne kaller det) var på Universitetet. Vi lærte om relasjons-databaser, og ble fortalt at det også fantes databaser som lagret objektstrukturer, men at det var noen rare greier som ingen egentlig brukte. Dette var noe vi bare kunne se bort ifra. (Det er uansett slik jeg husker det - mulig det ikke var foreleseren som sa det i det hele tatt).
Nå har jeg derimot lest meg opp på ODBMS, og sett at det er et stort miljø rundt dette, og at det brukes i mange, ulike miljøer. De siste par månedene har jeg begynt å bruke det selv også, og for meg er det en aldri så liten REVOLUSJON. ODBMS'er gir deg både en enkelhet jeg aldri har opplevd med andre verktøy, samtidig med en styrke på enkelte områder som ikke finnes i relasjonsdatabaser.
Men first thing first: Hva er en objektdatabase?
Mens relasjonsdatabaser lagrer data i form av rader i tabeller med relasjoner mellom data representert vha. primær- og fremmednøkler, så lagrer objektdatabasene objekter "slik de er". I en relasjonsdatabase vil Ordrelinjer ha en fremmednøkkel til en Ordre - i en objektdatabase vil en Ordre ha en liste med Ordrelinjer. Man navigerer i strukturen gjennom pointere eller referanser, som når vi programmerer, i stedet for joins basert på nøkler.
Styrken for oss utviklere er at databasemodellen er definert av objektene våre, ikke av kravene en relasjonsdatabase stiller. Dette gjør at det er mye enklere å kode mot et ODBMS. Ta for eksempel denne mer eller mindre vanlige kodesnutten for å lagre et domeneobjekt til en relasjonsdatabase gjennom ADO.NET:
public void SaveNewPerson(Person personToSave)
{
using (SqlConnection connection = new SqlConnection(_connectionString))
{
connection.Open();
SqlCommand command = connection.CreateCommand();
command.CommandText =
"INSERT into TPerson (" +
" FirstName, LastName" +
") VALUES (" +
"@FirstName, @LastName" +
")";
command.Parameters.Add("@FirstName", SqlDbType.NVarChar, 50);
command.Parameters.Add("@LastName", SqlDbType.NVarChar, 50);
command.Parameters["@FirstName"].Value = personToSave.GivenName;
command.Parameters["@LastName"].Value = personToSave.FamilyName;
command.ExecuteNonQuery();
}
}
Det eneste jeg har klart å gjøre på disse 23 linjene er å lagre et fornavn og et etternavn til databasen - helt uten transaksjons- og feilhåndtering - samtidig som jeg har introdusert rissiko ved å referere til databaseinformasjon (tabell- og feltnavn samt felt-type og størrelse) uten noen form for kontroll. Kompleksiteten i dette har fått annenhver utvikler til å designe sine egne dataaksess-løsninger som forsøker å gjøre dette enklere, hvor f.eks. kodegenerering var hot for noen år siden, og O/R-mappere er hot akkurat nå.
Skal du gjøre det samme med en O/R-mapper som NHibernate vil koden se omtrent slik ut:
public void SaveNewPerson(Person personToSave)
{
using (ISession session = _sessionFactory.OpenSession())
{
session.Save(personToSave);
}
}
Mye bedre, ikke sant?! Men det er noe som mangler her. For å få dette til å virke må vi konfigurere NHibernate til å skjønne hva som skal skje. Vi må lage en mapping-fil for Person-classen vår, som i prinsippet vil inneholde mye av det samme som koden i ADO.NET eksempelet. Fluent NHibernate kan gjøre dette enklere, men mappingen mellom databasen og objektene MÅ defineres på en eller annen måte. I tillegg må man lage en NHibernate konfigurasjonsfil for databasen. Jeg har forresten også utelatt opprettelsen av sessionFactory.
Ser vi på den tilsvarende koden for en objektorientert database, ser vi at det ligner svært mye på NHibernate-eksempelet:
public void SaveNewPerson(Person personToSave)
{
using (IObjectContainer db = Db4oFactory.OpenFile(_databasePath))
{
db.Store(personToSave);
}
}
Og her har jeg ikke utelatt noe annet en using-deklerasjonene i toppen av fila. Databasen jeg oppretter tar i mot et hvilket som helst C# objekt - det inneholder ingenting spesielt, men basen vet likevel akkurat hva den skal gjøre med den. Enklere kan det rett og slett ikke bli.
Dette er første del av en artikkelserie om objekt-orienterte databaser, basert på mitt NNUG-foredrag om samme tema. Fortsettelsen følger om en dag eller to..
Knagger: db4o, NNUG, nhibernate