2005-11-04

Active Record antipattern

Update 2009.05.22: Szerencsére ma már vannak jól kialakult perzisztencia framework-ök (a JPA-ról nem is beszélve) és a kultúrált programozók DAO-kkal dolgoznak, de nemrég találkoztam olyan emberrel aki éles projektben komolyan használni akarta ezt a design pattern-t. Ráadásul egy másik postban más okból is linkeltem erre a rövid kis agymenésre, szóval kicsit megcizellálom.

Szóval az Active Record design pattern dióhéjban arra épül, hogy az objektum maga tudja, hogyan kell őt beolvasni vagy legalábbis kimenteni valami adatbázisba. Tehát van save metódusa, ilyesmik.

Első probléma, hogy rögtön kell az objektumba injektálni valami adatbázisközeli logikát, ami másrészről nem jó helyen van ott, hiszen fölösleges függőséget hoz be a domain modell entitásaiba. Ha nem elég flexibilisre van megcsinálva ez a logika akkor a hordozhatóságnak annyi, pl. nem lehet kicserélni alatta a perzisztencia réteget. Van erre két megoldás: egyik hogy örököljük ősosztályból a logikát, nade ekkor is felülről (még rosszabb) betámadnak az implementációs részletek a domain modellbe. Ebben az esetben ráadásul el kell lőni az egyetlen öröklési lehetőséget (Java, C#) az implementációs részletek miatt. Na és mi van ha még a domain modell miatt is kéne örökölni? Pech. Másik megoldás a logika delegálása. Igen, ez működhet csak ügyelni kell rá hogy az objektumokhoz hozzá legyen cuppantva a logika ami kimenti őket.

Második probléma, hogy ha ezeket az objektumokat neadjisten másra is használni kell, felbugyoghatnak a szervíz rétegből a GUI logikába, félreeső modulokba. Például valaki véletlenül mond ott egy delete-et és szevasz aktív rekord - automatikusan mindenki számára publikálja a műveleteket. Egy jól leválasztott szervíz réteg esetén jól meg lehet határozni, hogy melyik modul milyen interfészt kap meg.

Hozhatnék még fel ilyen kérdéseket, hogy vajon minden adatbázis rekordhoz van egy aktív rekordunk a memóriában? Pazarlás. Minek? Stb.

A régi posthoz még annyi bevezető, hogy néha (ez már WTF kategória) a konstruktor meghívásával generálják az adatbázis rekordot, tehát a new Person('pcjuzer') valóban adatbázis műveletet von maga után. Csakhogy...

Innen pedig a régi post:

A konstruktornak van egy olyan tulajdonsága, hogy a lefutása után csakis egy újonnan inicializált objektum referenciáját tudja visszaadni. Tehát sem null-t, sem a hívás előtt létező objektum referenciáját nem tudja visszaadni.

Bevett gyakorlat, hogy egy objektumpéldányhoz egy adatbázis rekord tartozik. (Egyébként nagyon okos neve van a megoldásnak: Active Record. Annyira okos, hogy még céget is elneveztek róla.) Ha ezt az objektumpéldányt a konstruktorral akarjuk kiolvasni az adatbázisból, akkor több konstruktorhívás több példányt fog eredményezni ami ugyanarra a rekordra mutat. Ez egyrészt erőforrászabáló másrészt inkonzisztens állapotot lehet előidézni vele, ha a két példányt megváltoztatjuk és ezután visszaírjuk az adatbázisba.

Ehelyett factory metódust vagy osztályt kell használni, ami egy pool-ból kiveszi a már egyszer feltöltött rekordok objektumait. Legalábbis az a.r. design pattern-en belül.

Nincsenek megjegyzések: