Článek je volně přeložen z anglického originálu ORM is an anti-pattern, který napsal Laurie Voss (technický vedoucí awe.sm, dříve např. Yahoo! Widgets) a vydal ho na svém blogu pod licencí CC-BY-NC-SA. Vydáváme ho zde se souhlasem autora a pod stejnou licencí.
Minulý týden jsem tweetnul o ORM, a od té doby se mě několik lidí ptalo, jak jsem to myslel. Já jsem sice o ORM už psal, ale to bylo v kontextu větší diskuse o SQL a já bych to nerad míchal dohromady. Takže v tomto textu se budu držet pouze problematiky ORM. Vynasnažím se být stručný, protože z předchozích článků o SQL vím, že lidé mívají sklon po první větě, která je naštve, přestat číst a rovnou komentovat (i kdyby třeba dál v textu bylo tvrzení vysvětleno).
Co je to antipattern?
Potěšilo mě, když jsem našel na Wikipedii obsáhlý seznam antipatternů, jak ze světa programování, tak i mimo něj. Proč považuju ORM za antipattern? Protože splňuje hned dvě kritéria, která podle autora knihy AntiPatterns odlišují antipatterny od špatných návyků. Konkrétně:
- Na začátku se jeví jako přínosné, ale postupem času se objevuje víc špatných důsledků, než těch dobrých
- Existuje alternativní řešení, které je ověřené a znovunasaditelné
Právě ten první faktor vedl, podle mého, k obrovské popularitě ORM: na začátku to vypadá jako dobrý nápad, a když se postupem času objevují problémy, už je pozdě něco měnit.
Co označuji jako ORM?
Hlavní cíl mé kritiky je ActiveRecord, zpopularizovaný v Ruby on Rails a portovaný do půltuctu dalších jazyků. Ale stejně tak lze většinu výhrad vztáhnout i na jiné ORM, jako Hibernate v Javě a Doctrine v PHP.
Výhody ORM
- Jednoduchost: některé ORM nástroje vám budou tvrdit, že „odstraní nutnost znát SQL“. To je slib, který žádný z těch, co jsem viděl, nesplnil. Jiné jsou realističtější a prohlásí, že redukují množství SQL, ale dovolí vám jej používat, když bude potřeba. S jednoduchým modelem a s projektem v počátcích je to rozhodně výhoda: s ORM budete vyvíjet rychleji a efektivněji, o tom nepochybujte. Dost možná si ale zaděláte na problémy v budoucnu.
- Generování kódu: odstraněním uživatelského kódu z modelů pomocí ORM otevřelo cestu generování kódu, „scaffoldingu“, které vám dá funkční rozhraní ke všem tabulkám na základě jednoduchého popisu schématu. Navíc můžete schéma změnit a vygenerovat kód nový. Zase – funguje to perfektně na začátku.
- Efektivita je „good enough“: žádný z ORM, které jsem viděl, neprohlašuje, že zvýší efektivitu. Všechny říkají, že upřednostňují jednodušší kód před výkonem. A když náhodou začne být výkon nedostatečný, můžete přece nahradit pomalé volání ORM ručně napsaným SQL, není-liž pravda?
Problémy s ORM
Neodpovídající abstrakce
Nejzjevnější problém u ORM je, že jde o abstrakci, která nedostatečně abstrahuje od implementačních detailů. Dokumentace všech velkých ORM knihoven je běžně plná odkazů na koncepty z SQL. Někdy jsou představeny, aniž by bylo řečeno, že mají ekvivalent v SQL, jindy jsou zase představovány jako funkce pro generování SQL.
Celý vtip abstrakce spočívá v tom, že by měla zjednodušovat. Abstraktní vrstva nad SQL, která předpokládá, že znáte SQL, vlastně zdvojnásobuje množství věcí k naučení: musíte umět SQL, abyste chápali, jak položit dotaz, a musíte umět přimět svoje ORM, aby ho za vás položilo. Například v Hibernate se ještě učíte třetí jazyk, HQL, což je téměř-úplně-ale-ne-naprosto SQL, a to se pak do SQL překládá.
Zastánci ORM řeknou, že to neplatí pro každý projekt, že ne každý potřebuje složité JOINy, že ORM je řešení „80/20“, protože 80 procent uživatelů potřebuje pouhých 20 procent možností SQL, a právě to ORM zařídí. Ze své patnáctileté praxe vývojáře databázových projektů mohu říct, že tohle u mne neplatilo. Pouze na samotném začátku vývoje se můžete obejít bez JOINů nebo s naivními JOINy. Později se dostanete k tomu, že je nutné dotazy konsolidovat a vyladit. I kdyby 80 % uživatelů potřebovalo 30 procent schopností SQL, tak 100 % uživatelů nakonec bude muset ORM abstrakci obejít, aby jim to nakonec fungovalo.
Nesprávná abstrakce
ORM vám bude skvěle fungovat v případě, že opravdu nepotřebujete žádné relačně-datové funkce, ale v takovém případě máte jiný problém: používáte špatné datové úložiště. Režie RDBMS je enormní; to je jeden z nejdůležitějších faktorů, proč jsou NoSQL úložiště o tolik rychlejší. Pokud jsou vaše data relační, tak se tahle režie vyplatí: vaše databáze nejen ukládá data, ona je reprezentuje a dokáže odpovědět na otázky ohledně těchto dat, a dokáže to díky zachycení vztahů mnohem efektivněji než procedurální kód.
Pokud vaše data relační nejsou, pak jen přidáváte spoustu nepotřebného zpomalení – v první řadě je pomalá RDBMS, a nad ní ještě postavíte další zpomalující vrstvu v podobě ORM.
Na druhou stranu pokud vaše data relační JSOU, tak se nakonec mapování na objekty rozbije. SQL je založené na relační algebře: výstupem SQL není objekt, ale odpověď na dotaz. Pokud váš objekt „je“ instancí X a „má“ několik Y, a každé Y „patří“ Z, jaká je správná reprezentace takového objektu v paměti? Měly by to být jen vlastnosti X, nebo by měly obsahovat i Y a/nebo i všechna Z? Pokud budete mít pouze vlastnosti X, kdy se dotážete na Y? A budete potřebovat jedno Y, nebo všechna? Ve skutečnosti to záleží na okolnostech – to je to, co mám na mysli, když píšu, že SQL odpovídá na otázky. Reprezentace objektu v paměti záleží na tom, co se s ním chystáte dělat. OO design nenabízí nic jako „kontextově senzitivní reprezentaci“. Zkrátka: Relace nejsou objekty. Objekty nejsou relace.
Zabitý tisícem dotazů
Důsledkem je další problém ORM: neefektivita. Když si vyzvednete objekt, které z jeho vlastností (sloupců tabulky) budete potřebovat? ORM to neví, tak si řekne o všechny (nebo požaduje, abyste specifikovali sami, čímž rozbíjí abstrakci). Na začátku to není problém, ale když začnete načítat tisíce záznamů najednou, každý s 30 sloupci, z nichž potřebujete jen tři, máte smrtelný zdroj neefektivity. Mnoho ORM nástrojů je také výrazně slabých ve vytváření JOINů a raději pokládají tucet dotazů k jednomu objektu. Jak jsem už psal výše, mnohé ORM výslovně říkají, že nejsou zaměřené na efektivitu, a některé nabízejí způsob, jak problémové dotazy vyladit. Problém, který jsem v praxi pozoroval, je ten, že málokdy najdete jediný dotaz, „stříbrnou kulku“, kterou stačí optimalizovat. Smrtící pro databázové aplikace není efektivita jednoho konkrétního dotazu, ale množství dotazů. ORM chybí kontext, takže nedokáže dotazy správně konsolidovat a musí se uchylovat ke kešování a dalším mechanismům, které mohou tento nedostatek kompenzovat.
Jaké jsou alternativy?
Doufám, že se mi podařilo předvést některé případy, kde mají ORM zásadní návrhové nedostatky. Ale k tomu, aby byly antipatternem, potřebují mít alternativu. Ve skutečnosti mají hned dvě:
Používejte objekty
Jestliže jsou vaše data objektová, přestaňte používat relační databázi. IT je v současnosti zaplavené různými key-value úložišti, které umožňují uchovávat elegantní datové struktury ve velkých počtech a přistupovat k nim rychlostí světla. Není žádný programátorský zákon, který by říkal: „Krok č. 1 při psaní webové aplikace je nainstalovat MySQL“. Masivní nadužívání relačních databází na každou situaci, kdy je potřeba uložit data, je jedním z důvodů, proč si SQL získává v posledních letech tak špatnou pověst. Ve skutečnosti je příčinou líný návrhář, který nehledal vhodné úložiště, ale použil RDBMS.
Použití SQL v modelu
V programování je extrémně nebezpečné prohlašovat, že k něčemu existuje Jediná Správná Cesta™ jak to udělat. Podle mých zkušeností je nejlepším způsobem, jak reprezentovat relační data v objektově orientovaném kódu, stále pomocí modelu: zapouzdření datové reprezentace do jediného místa v kódu je dobrý nápad. Ale pamatujte, že úkolem vaší modelové vrstvy není reprezentovat objekty, ale odpovídat na otázky. Vytvořte si API, které odpovídá na otázky, co mu pokládá aplikace, a udělejte to tak jednoduché a efektivní, jak je potřeba. Někdy budou odpovědi svérázné, takovým způsobem, který i novopečený OO vývojář označí za „špatný“, ale postupem času budete nalézat stále lepší průsečíky obou světů, což vám dovolí refaktorovat dotazy do efektivní podoby.
Někdy bude výstupem jednoduchý objekt X, který je snadné reprezentovat. Ale jindy může být výstupem matice agregovaných dat, nebo naopak jediné celé číslo. Odolejte pokušení zabalit všechno tohle do mnoha vrstev abstrakce, a přistupujte k datům podle jejich vlastní přirozenosti. Především odolejte klamu OO, že dokáže reprezentovat naprosto cokoli a všechno. OO samotné je abstrakcí, hezkou a velmi ohebnou, ale právě relační data jsou jednou z hranic OO. Předstírání, že objekty mohou dělat něco, co dělat nedokážou, je základní problém v každém ORM.
Shrnutí (TL;DR)
- ORM je na začátku snadný k pochopení a vývoj s ním je rychlejší než psaní modelů se SQL
- Jeho efektivita je na počátku dostatečná
- Naneštěstí se s růstem komplexnosti projektu projeví nedostatky: abstrakce je rozbitá a nutí vývojáře používat pokročilé SQL
- V nadsázce tvrdím, že se abstrakce ORM rozbije ne ve 20 % projektů, ale skoro ve všech.
- Objekty nejsou správná cesta, jak vyjádřit výsledky relačního dotazu
- Nemožnost jednoznačného mapování dotazů na objekty vede k zásadním neefektivitám v aplikacích, které používají ORM. Tyto neefektivity prostupují celý projekt, jsou distribuované a proto nemohou být odstraněny ani napraveny jinak, než kompletním opuštěním ORM.
- Zkuste se pořádně zamyslet nad designem aplikace dřív, než mechanicky nasadíte relační databázi a ORM
- Pokud jsou vaše data přirozeně objektová, tak použijte objektová úložiště („NoSQL“). Budou mnohem rychlejší než SQL s ORM.
- Pokud jsou vaše data z podstaty relační, pak se zvýšená režie RDBMS vyplatí.
- Zapouzdřete SQL dotazy do modelové vrstvy, ale navrhněte svoje API tak, aby vracelo data, potřebná pro vaši aplikaci; nepodlehněte pokušení příliš generalizovat.
- Objektově orientovaný design nedokáže reprezentovat relační data efektivním způsobem; to je zásadní omezení OO designu a ORM ho nedokáže napravit.
(Podobný pohled nabízí i článek The Vietnam of Computer Science, kde je použití ORM svým průběhem přirovnáváno k Vietnamské válce. – pozn. překl.)
Přehled komentářů