Patternskolen del 8 – Active Record vs Repository

I denne artikkelen tar vi for oss to forskjellige patterns for håndtering av dataaksess, nemlig Active Record (kjent bl.a. fra Ruby on Rails) og Repository (kjent fra bl.a. boka Domain Driven Design av Eric Evans).

Bjørn Herve Moslet

Forrige gang så vi på Decorator-patternet i forhold til arv, slik vi kjenner det fra objektorientering.

Denne gangen tar vi for oss to forskjellige patterns for håndtering av dataaksess, nemlig Active Record (kjent bl.a. fra Ruby on Rails) og Repository (kjent fra bl.a. boka Domain Driven Design av Eric Evans). For å illustrere implementasjonene tar jeg utgangspunkt i en veldig enkel domeneklasse for Person:

Person

Active Record pattern

Kort fortalt: Active Record pakker inn en rad fra en databasetabell, innkapsler dataaksessen og legger til domenelogikk på dataene.

Eksempelimplementasjon i C#:

class Person
{
string Fødselsnummer { get; set; }
string Navn { get; set; }

static Person[] HentAlle()
{ /* databasekode */ }
static Person Hent(string fødselsnummer)
{ /* databasekode */ }
void Lagre()
{ /* databasekode */ }
void Slett()
{ /* databasekode */ }
}

Her ligger metoder for dataaksess på domeneklassen. Metoder for å lese ett eller flere objekter vil vanligvis være statiske, mens metoder for lagring og sletting opererer på den gitte instansen.

Repository pattern

Et repository ligger mellom domenet og datalaget. Det tilbyr et collection-lignende grensesnitt mot en database. Et repository er implementert separat fra domenet og slik oppnår man separasjon av domenelogikk og dataaksess. Ofte er grensesnittet til et repository definert i et interface.

Eksempelimplementasjon i C#:

class Person
{
    string Fødselsnummer { get; set; }
    string Navn { get; set; }
}
 
interface IPersonRepository
{
    Person[] HentAlle();
    Person Hent(string fødselsnummer);
    void Lagre(Person person);
    void Slett(Person person);
}
 
class PersonRepository : IPersonRepository
{
    Person[] HentAlle()
    { /* databasekode */ }
    Person Hent(string fødselsnummer)
    { /* databasekode */ }
    void Lagre(Person person)
    { /* databasekode */ }
    void Slett(Person person)
    { /* databasekode */ }
}

Fordeler og ulemper

En fordel med Active Record er at det blir veldig lite ekstra kode for å gjøre dataaksess. Alternativt må denne koden flyttes ut i en egen Repository-klasse og vi må trekke ut et interface for den i tillegg.

En annen fordel som ofte fremheves med Active Record, er at all koden tilhørende et objekt blir samlet i samme klasse. Både domenelogikk og dataaksess ligger på samme sted. Det gjør det enkelt å konsumere koden og å endre den. Dette medfører innkapsling, som vi har lært er en bra ting i objektorientering. På den andre siden bryter Active Record med flere andre prinsipper: En klasse skal bare gjøre én ting (single responsibility principle) og kode som ikke hører sammen bør adskilles (separation of concerns). Vi må altså gjøre endringer i domeneklasser hvis vi endrer koden for databaseaksess. En kan derfor argumentere for at Active Record fører til god objects; det vil si at domeneklassene har for mange ansvarsområder eller gjør for mye. Repository, derimot, følger alle disse prinsippene.

Den definitivt største ulempen med Active Record er at den gjør enhetstesting vanskelig. Repository, kombinert med dependency injection, gjør det lett å lage en mock for repositoryets interface og slik teste uten å gå mot en faktisk database. For Active Record er det vanskelig å mocke vekk dataaksesslogikken som ligger i domeneobjektet, og særlig når enkelte av metodene er statiske. Dette lar seg riktignok løse ved f.eks. å bruke Static gateway, som vi har sett på tidligere.

Med mindre jeg bruker et rammeverk som setter føringer for dataaksess, vil jeg personlig alltid velge Repository. Fordelene er så mange at de overveier eventuelle ulemper i alt annet enn de mest trivielle systemer.

Temaer