Chcel by som sa tu podeliť s mojimi skúsenosťami a problémami s používaním ORM knižníc pre prístupu k databázam v komerčnej praxi.
Vo svojej firme už viac ako desaťročie používam databázy ako úložiská trendových a štatistických priemyselných meraní. Je to využitie len triviálneho zlomku možnosti databáz, len s minimálnym využitím relácii a absolútne bez SP, trigerrov a ošetrovaní zabezpečenia integrity. Možno aj preto sme sa pred asi dvomi rokmi rozhodli že si pre vlastné potreby vyvinieme vlastné ERP (v našom prípade je to skôr len také ERP-lite). Na trhu sa nachádza nepreberné množstvo ERP systémov (aj opensourcových edícií) s prepracovaným webovým prístupom. My sme sa však rozhodli urobiť vlastný systém skôr pre edukačný účel s cieľom precvičiť sa v RDBMS. Pre malú firmu ako mám ja, je úplne jedno že som to spravil v delphi ako čisto aplikáciu pre Windows. Napriek tomu že sa jedná ešte len o prvý level komerčného nasadenia, teda vývoj aplikácie pre nasadenie vo vlastnej firme (aplikácia tak nemusí byť celkom „bulletproof“ ako keď sa nasadzuje priamo u zákazníkov), tak myslím že sa to rozhodne oplatilo.
Od príchodu rozšíreného RTTI pre delphi sa mi veľmi začala páčiť myšlienka ORM a možnosti ktoré priniesla definícia vlastných TCustomAttribute, naozaj to vyzeralo v tých všetkých prezentáciách krásne. Takže som sa pustil do toho. Na začiatku som si prešiel niekoľko ORM knižníc pre delphi:
- TMS Aurelius
- DORM
- Na tomto odkaze sa tiež nachádzajú niektoré delphi ORM systémy: ORM for Delphi
- Nedávno sa na delphi trhu objavil ešte tento verím že zaujímavý ORM systém - EntityDAC. Zatiaľ som ho však bližšie neštudoval, ale očakávam že to bude kvalitný produkt vzhľadom na to že Devart už dlhé roky produkuje množstvo kvalitných databázových komponentov. Pozn. Radek: EntityDAC nabízí i free verzi a věřím, že v brzké budoucnosti o něm něco napíši.
Nakoniec ma v začiatkoch zlákal platený TMS Aurelius. Rozpis všetkých možností v ich produktovom manuáli naznačovali že táto ORM knižnica vie robiť presne to čo by som od ORM očakával. Po praktických skúsenostiach musím obhájiť že TMS Aurelius je na pomery spoločnosti TMS software kvalitný produkt, ale… Ale dnes už pracujem s inou ORM knižnicou (DORM) a stále si nie som istý či ja robím niečo zle, či len niečomu zle rozumiem, alebo celé ORM je proste ako parádne nadizajnované ferrari s perfektným lakom ale veľmi ťažkopádnym motorom vhodným akurát tak na testovacie okruhy ale nevhodným do každodennej prevádzky.
Dnes musím povedať že investíciu do TMS Aureliusu som si mohol ušetriť. Nie preto že by bol zle urobený, mnoho vecí ma naučil. Ale proste si neviem predstaviť prevádzkovať na bežnom ORM komerčnú aplikáciu, respektíve nie výhradne na ORM. Nechcem nikoho od ORM odrádzať, ORM má v aplikáciách svoje miesto a má niektoré svoje nevyvrátiteľné výhody. Ale človek veľmi rýchlo narazí na problém naozaj nehospodárneho narábania ORM s databázou. Prípadne na celkovú neohrabanosť tejto na prvý pohľad vynikajúcej koncepcie. Podľa môjho názoru nato aby sa ORM dal reálne používať, tak buď si ho vývojársky tým musí pre svoj projekt vyvinúť sám, alebo aspoň dosť intenzívne a cielene prispôsobiť potrebám projektu.
Takže späť k mojim skúsenostiam. Na začiatok na základe slušne spracovanej príručky k TMS Aureliusu sme vypracovali bohatý objektový model, ktorý sa namapoval k databázovým tabuľkám. V tomto bol TMS Aurelius veľmi schopný. Na základe tohto modelu dokázal vytvoriť samostatne celú databázu. Povytváral tabuľky, indexy, cudzie kľúče. Dokázal dokonca urobiť aj „rebuild“ celej databázy a v prípade zmien objektového modelu dokázal v niektorých prípadoch upraviť štruktúru už naplnenej databázy bez straty dát. No opäť toto sú pekné funkcie vynímajúce sa vo „features liste“ na prezentačnom webe ponúkaného systému. Ale v komerčnej praxi si nedokážem predstaviť pošetilca ktorý by zveril vytvorenie, alebo nedajbože preskladanie produkčnej databázy nejakému automatickému systému. Ale v rámci pokusov a práce s examplami sa tieto funkcie hodili, ale v reálnej produkčnej praxi sa im určite každý vyhne.
Ďalší problém na ktorý som narazil je že práve nami bohato nadefinovaný objektový model i zo všetkými väzbami na jednu stranu veľa štandardných operácií náramne uľahčoval ale v momente keď bolo potrebné sa dopracovať k nejakým menej štandardným a špecializovaným operáciám, tak práve tento bohatý model sa ukázal ako silno obmedzujúci. A povedzme si na rovinu, každý, ale každý reálny projekt sa skladá zo súboru štandardných resp. triviálnych operácií a zo súboru viac či menej špecializovaných a sofistikovaných operácií a postupov. Vo verzii kedy som TMS Aurelius používal naposledy, bol tento systém voči špeciálnym požiadavkám dosť nepriateľský a nepoddajný. Námahu ktorú nám na začiatku ušetril, si neskôr viacnásobne vypýtal späť.
Nechcem tým povedať že TMS Aurelius je zlý ORM systém, ale niekedy menej je viac. A mám potvrdené aj od iných vývojárov že snaha rôznych systémov uľahčiť produkciu aplikácie na minimum pridávaním rôznych automatických „vychytávok“ vedie iba k tomu, že to veľmi pekne vyzerá v demo aplikáciách, ale v praxi to spôsobuje viac problémov ako úžitku. A väčšinou je nutné práve tieto automatické „vychytávky“ povypínať, pretože sa potom človek nestačí čudovať čo mu ten systém pod rukami robí. Dospel som skôr k tomu, že je lepšie keď je ORM systém skôr jednoduchý blbec, ktorý pomáha generovať SQL dotazy a plniť objekty a to v podstate plne pod vývojárovou kontrolou, než keď sa správa ako prepracovaný automat.
Takže z tohto titulu sme prešli na opensourcový DORM, ktorý nie je ani zďaleka tak prepracovaný ako TMS Aurelius, ale podľa môjho názoru je podstatne ohýbateľnejší a navyše sa mi na ňom veľmi páči že DORM, ako jeho autor uvádza, je perzistenčný ignorant. Môže to byť trošku nebezpečné pre začiatočníkov, ale keď vývojár má potuchy že v akých vodách pláva, čo v komerčnej praxi by mal mať, tak to môže vcelku šikovne využiť vo svoj prospech. Bohužiaľ mám pocit že DORM je v súčasnosti vyvíjaný vyslovene pre vlastné potreby autorov a už menej pre širšiu komunitu vývojárov.
Ale aj napriek tomu musím povedať že jadro DORMu je už v celku stabilné a nenarazili sme na žiaden problém. Dokumentácia je veľmi stručná a len minimálne aktualizovaná. Ale DORM sa ukázal ako veľmi intuitívny a drvivá väčšina sa dala naučiť priamo z demo projektov a z unit testov. Driver pre akúkoľvek inú databázu napriek absencii serióznej dokumentácie som si dokázal napísať za jeden pracovný deň (to som sa ešte s knižnicou len zoznamoval, potom sa to dalo stihnúť i za niekoľko málo hodín). Priznám sa že na tom som sa aj vcelku vyblbol. Vyrobil som si v rýchlosti adaptér pre ZeosLib, fungovalo to veľmi pekne. Autor udržiava aj unit testy ktorými som si vždy mohol svoje úpravy skontrolovať. Potom som si vyrobil adaptér pre FireDAC napriek tomu že jeden tam už je. Je funkčný ale bolo vidno že bol ušitý horúcou ihlou len pre komunitu, ale sám autor ho moc nepoužíva. Ja som však ten svoj adaptér založil na physical Data Access Engine (Phys) vid. Radkov článok FireDAC-z-praxe-3. Považujem za hodne barbarské vtrepať do ORM ako medzivrstvu medzi DB a objekty taký objemný systém ako je TDataset. Nejaký low-level prístup na prenos dát medzi DB a objektmi je v tomto prípade oveľa vhodnejší.
V praxi najväčší problém na ktorý sme narazili bola hodne silná nehospodárnosť ORM systému čo sa týka generovania SELECT dotazov a to hlavne ich množstva. Podstatou ORM systému je to, že sa v jeho objektoch (entitách) dajú nadefinovať relačné väzby („has one“, „has many“, „belongs to“). To je v podstate najsilnejší ale zároveň najzradnejší rys ORM. Pre relácie je precízne nastavenie takzvaného "Lazy" loadingu nevyhnutnosťou (kvôli optimalizácii počtu generovaných selectov), ale nerieši to všetky praktické problémy s ktorými sa v človek bude v praxi stretávať.
Väčšina ORM systémov vcelku pekne generuje selecty pre takzvané "belongs to" relácie. Napríklad chcem získať z databázy zákazku a k nej aj objekt zákazníka pre ktorého sa ta zákazka robí. Je to relácia 1:1 a ORM vygeneruje pekný jeden select s JOINom. Problém ale prinášajú tzv. „has many“ relácie, čiže relácie 1:N. Ešte v poriadku to pracuje ak chcem získať z databázy jeden objekt ktorý má jednu alebo viacero „has many“ relácií (napr. zákazníka ku ktorému patrí kolekcia zákaziek, servisných zásahov, zmlúv atd.). ORM vygeneruje zvyčajne jeden select pre toho jedného konkrétneho zákazníka a následne podľa nastavenia lazy loadingu ešte po jednom selecte ku každej „has many“ relácii (čiže pre jednotlivé zákazky, servisné zásahy, zmluvy atd.). Veľmi sa ale treba mať na pozore pokiaľ sa snažíme z databázy získavať zoznamy objektov ktoré obsahujú „has many“ relácie. A to o to viac ak tieto zoznamy obsahujú viac vnorených úrovní „has many“ relácií, tu to môže byť naozaj kritické.
Zmienenú situáciu rieši vraj „lazy loading“, to vám budú tvrdiť v každej príručke ku ORM systému. Lenže v praxi sú bežné situácie kedy je potrebné potlačiť „lazy loading“. ORM systém to zvyčajne podporuje, ale z mnou skúšaných ORM systémov to vo verziách ktoré som skúšal žiaden z nich neriešil nejakou cestou optimalizácie ale proste len prasácky odpaľoval selecty pre všetky objekty a pre ich relácie. Žijeme však v dobe silnej informatizácie a už značne výkonných strojov. Takže veľakrát si chceme dovoliť zobrazovať komfortné zoznamy a k tomu nám RDBMS ponúka viacero možností, takže otázne je či ich vie využiť aj používané ORM.
Rozoberme si to názorne. Chceme napríklad opäť získať zoznam zákaziek pre aktuálny rok a ku každej z nich hneď aj zoznam faktúr, objednávok, nákupov atd. Čiže chceme v aplikácii zobraziť komfortný zoznam hneď aj s okolitými dátami ku každej zákazke (hodnoty faktúr, ich splatenie prípadne termín po splatnosti, hodnoty nákupov, výšku zisku atd…). Na získanie dát pre takto komfortný zoznam musíme „lazy loading“ v ORM zámerne a vedome potlačiť a donútiť ho k okamžitému načítaniu aj priradených relácií. Ide teraz o to, ako efektívne sa k tomu dokáže ORM postaviť. V tom najhoršom prípade (a bohužiaľ aj v tom najčastejšom) to ORM vykoná tak ako som už naznačil. Jedným selectom získa z databázy ten zoznam zákaziek a naplní nimi nejaký TObjectList alebo niečo podobné. Následne prechádza jednotlivé zákazky v tomto liste a ku každej „has many“ relácii každého jedného objektu odpaľuje vždy po jednom selecte a ak dáta získané z relácie obsahujú ďalšie vnorené „has many“ relácie, tak tento postup zase opakuje jednotlivo ku každému získanému relačnému objektu/zoznamu. Proste čistá tragédia, počet selectov narastá v podstate geometrickou radou. I v mojej malej firme, kde mám par zákaziek do mesiaca, sa počet týchto selectov pri získavaní jediného takéhoto zoznamu dal počítať na stovky a to rok ešte len začínal. Neviem si už ani predstaviť koľko selectov by to prinieslo koncom roka. Takže taký e-shop v ktorom sa udejú stovky nákupov denne by pri takom prístupe skolaboval už po niekoľkých dňoch.
Netvrdím že takto sa správa každé ORM, ale mnou skúšané ORM systémy sa mi proste nepodarilo prinútiť k efektívnejšiemu správaniu, aspoň teda nie vo verziách v ktorých som ich testoval. To že s tým nemám problém len ja dokazujú aj nasledujúce odkazy kde sa s obdobným problémom s nehospodárnosťou generovania selectov tiež zaoberajú (hoci sa už nejedná o delphi):
php.vrana.cz/snizeni-poctu-dotazu-v-orm.php
Takže ak zvažujete nákup niektorého ORM systému rozhodne by som odporúčal v prvom rade preveriť ich schopnosť vysporiadať sa s práve uvedeným problémom. Ručne by som totiž dáta pre uvedený zoznam dokázal dostať pomocou asi troch, štyroch selectov. Jeden pre zoznam zákaziek aktuálneho roka a potom pomocou joinov pre každú „has many“ reláciu by som použil po jednom selecte. Vrátené resultsety by som potom poprechádzal a popriraďoval ku prislúchajúcim objektom teda zákazkám. Veľa ORM systémov túto povinnosť prenáša na databázu, ale iste sa zhodneme na tom že cena je privysoká, priam neakceptovateľná. Aspoň teda nie v komerčnom prostredí (3 selecty verzus 1800 selectov?!). Pokiaľ si doma nejaký nadšenec robí aplikáciu pre zoznam svojich hudobných albumov, tam by sa to dalo možno akceptovať. Takéto domáce aplikovanie ORM už pekne zvládnu väčšinou aj priložené demá ku ORM systémom.
Ďalším problém je získavanie rôznych agregačných dát. Takéto dotazy sa nie vždy „zmestia“ do navrhnutého objektového modelu entít ORM. Tu sa vcelku osvedčil mnou použitý DORM s jeho perzistenčnou ignorantnosťou. Autor uvádza že ho tento rys stál veľa námahy, ale považuje ho za veľmi podstatný a ja s ním musím len súhlasiť. Proste ORM sa nečuduje keď mu podhodím k naplneniu objekty ku ktorým proste neexistuje žiadna prislúchajúca tabuľka, alebo aj existuje ale nemá zhodnú štruktúru. Samozrejme že vtedy mu musíme nejakým spôsobom dodať aj príslušný SQL dotaz, aby to s tým podstrčeným objektom proste sedelo.
Takže sme po týchto skúsenostiach dospeli k tomu, že pre vážne používanie ORM v podnikových aplikáciách je nevyhnutné nasadiť buď nejaký silný „enterprise“ ORM systém so zameraním na ozajstné komerčné prostredie (je otázne či je možné považovať ORM v cene do 200€ za seriózny „enterprise“ ORM systém, ale nechcem tvrdiť že by to nebolo možné). Alebo je nutné si značne prispôsobiť ORM systém pre potreby toho ktorého projektu. Ďalšou možnosťou je používať kombinovaný prístup. Teda pristupovať do databázy jednak pomocou ORM a jednak pomocou ručne, alebo poloautomaticky tvorených dotazov a pracovať s dátami vrátenými pomocou štandardných resultsetov. No a ak si mám nejaký ORM systém prispôsobovať, alebo masívne obchádzať, tak nevidím moc dôvodov prečo by som mal za neho platiť. Tu sa mi naozaj osvedčil ľahko obsluhovateľný a modifikovateľný DORM.
Z vyššie uvedeného je ale jasné že aplikácia stratí aj jednu z ďalších výhod ORM a tou je možnosť jednoduchého prepnutia aplikácie na iný RDBMS. Ale prosím, v komerčnom prostredí si aj tak nikto nedovolí prepnúť sa na inú databázu jednoduchou zmenou konfigurácie aplikácie. Vždy to bude sprevádzané hromadou testov, takže nejaké tie úpravy v aplikácii budú aj očakávateľné.
Vo výsledku by som ale rozhodne ORM nezatracoval, len si treba dobre uvedomiť že to nie je všetko riešiaci koncept a rozhodne si netreba naivne myslieť že celú aplikáciu je možno postaviť rovno na ňom. Nemám síce skúsenosť zo všetkými na trhu dostupnými ORM systémami, ale treba rátať s tým že sa nevyhneme aj priamemu prístupu k databáze. No na druhú stranu práca s nejakým databázovým objektom aj s nejakou rozumnou hierarchiou jeho kolekcií cez ORM vie byť naozaj luxusná. Práca s objektmi v rámci formulárov, ich pridávanie, odoberanie a editácia je veľmi príjemná. ORM na základe zmien automaticky generuje korektné UPDATy a INSERTy. To všetko je možné s dobrým ORM bez problémov prevádzkovať, len si netreba naivne myslieť že všetko zvládne za vás. Rozhodne mu treba pozerať na prsty a nezriedka ho aj usmerňovať. Ako som už spomínal, ORM podľa môjho názoru nesmie byť „preautomatizovaný“, je to viac na škodu ako k úžitku. A toto si myslím o väčšine IT systémov, knižníc a doplnkov. V prípade vývoja komerčnej aplikácie sa mi vždy oplatilo mať čím viac záležitostí pod vlastnou kontrolou než aby som to zveroval nejakému systému napriek tomu že to na prvý pohľad bolo prácnejšie. Veľakrát ale boj s neželanou automatikou je nakoniec prácnejší, nehovoriac o prekvapeniach ktoré niekedy vie nekontrolovaná automatika priniesť až neskôr pri plnom nasadení.
Daniel Andraščík, ALPHA SYSTEMS s.r.o.