Mým oblíbeným článkem, hlavně kvůli analogii z historie, je The Vietnam of Computer Science, který před již mnoha lety napsal Ted Neward. S jeho laskavým svolením přináším překlad. Dlouho jsem si na něj netroufal, ale nakonec jsem učesal výsledek z DeepL. Překlad uvolňuji pod licencí Creative Commons BY-NC-SA 3.0, nicméně autorská práva stále náleží Tedu Newardovi.

Nabízím analogii k objektově-relačnímu mapování a problémům, které představuje

26. června 2006

(Na Microsoft TechEd 2004 v San Diegu, jsem se na akci po konferenci účastnil rozhovoru s Harrym Piersonem a Clemensem Vastersem, a jak je typické, když se my tři sejdeme, předmětem našich diskusí byla architektonická témata. Kolem nás se shromáždil dav lidí stejných zájmů. Přišla řeč na technologie objektově-relačního mapování a tehdy jsem poprvé použil větu: „Objektově-relační mapování je Vietnamem informatiky“. V mezidobí jsem obdržel řadu žádostí o upřesnění diskuse, která se za tímto výrokem skrývá, a vzhledem k nedávnému oznámení Microsoftu ohledně „podpory entit“ v ADO.NET 3.0 a přijetí Java Persistence API jako náhrady za EJB Entity Beans i JDO se zdálo, že je čas přesně to udělat.

(2022 Poznámka: Tento článek je již patnáct let starý. Několikrát jsem byl v pokušení historickou část zkrátit - to je jedna z nejčastějších výtek k tomuto dílu -, ale v zájmu toho, aby po světě nekolovalo více jeho kopií, jsem ji zde ponechal. Pokud chcete lekci historie přeskočit, zde je rychlý odkaz na technologické kousky.)

Žádný ozbrojený konflikt v dějinách USA nestrašil americkou armádu více než Válka ve Vietnamu, známá jako Vietnam. Tolik odlišných prvků se spojilo, aby vytvořily nejrozhodnější bod obratu v moderních amerických dějinách, že se vzpírá snaze jakéhokoli laika je od sebe oddělit. Přesto je příběh Vietnamu v podstatě prostý: Spojené státy zahájily vojenský projekt s jednoduchými, ale nejasnými a protichůdnými cíli a rychle se zapletly do šlamastyky, která nejenže svrhla dvě vlády (jednu legálně, druhou silou zbraní), ale také hluboce poznamenala americkou vojenskou doktrínu na další čtyři desetiletí (přinejmenším).

Ačkoli se to může zdát banální, Objektově-relační mapování je Vietnamem informatiky. Představuje šlamastyku, která začíná dobře, postupem času se komplikuje a zanedlouho své účastníky uvězní v závazku, který nemá jasné ohraničení, jasné podmínky vítězství ani jasnou strategii úniku.

Historie

PBS má dobré shrnutí války, ale pro ty, kteří se zajímají spíše o informatiku než o politickou/vojenskou historii, je stručná verze následující:

Jižní Indočína, dnes známá jako Vietnam, Thajsko, Laos a Kambodža, má dlouhou historii bojů o autonomii. Před francouzskou koloniální nadvládou (která začala v polovině 19. století) bojovala Jižní Indočína o regionální nezávislost na Číně. Během druhé světové války oblast dobyli Japonci, aby ji později „osvobodili“ spojenci, což vedlo Francii k obnovení koloniální nadvlády (stejně jako Brity na jejich koloniálních územích jinde v Asii a Indii). Po Druhé světové válce však obyvatelé jižní Indočíny, kteří se zbavili jednoho utlačovatele, rozšířili své protiokupační úsilí na boj proti Francouzům namísto Japonců a v roce 1954 Francouzi kapitulovali a podepsali Ženevské mírové dohody, které Vietnamu formálně poskytly nezávislost. Bohužel globální tlaky toto úsilí poněkud zvrátily a místo trvalé mírové dohody vzniklo dočasné řešení, které rozdělilo zemi na 17. rovnoběžce a vytvořilo dva národy tam, kde dříve žádné takové rozdělení neexistovalo. V roce 1956 se měly konat volby, které měly zemi znovu sjednotit, ale USA se obávaly, že by těmito volbami získala příliš velkou moc Komunistická strana Vietnamu, a místo toho podpořily protikomunistický stát jižně od 17. rovnoběžky a vytvořily kolem něj řadu mnohostranných dohod, například SEATO. Zrodil se nový stát Jižní Vietnam a jeho prvním (pochybně) zvoleným vůdcem se stal Ngo Dinh Diem, přesvědčený antikomunista, který téměř okamžitě prohlásil, že jeho země je pod útokem komunistů. Eisenhowerova administrativa Diemovu vládu nadále podporovala, ale Diemova loajalita u lidu byla od počátku téměř nulová.

V době nástupu Johna F. Kennedyho z Demokratické strany USA do Bílého domu se situace v Jižním Vietnamu začala vyhrocovat. Kennedy vyslal do Vietnamu tým, který měl prozkoumat tamní poměry a pomoci formulovat strategii v této otázce. V dokumentu, který je dnes známý jako „Bílá kniha z prosince 1961“, byly předloženy argumenty pro zvýšení vojenské, technické a ekonomické pomoci spolu s rozsáhlými americkými „poradci“, kteří měli pomoci stabilizovat Diemovu vládu a zlikvidovat Frontu národního osvobození, kterou USA nazvaly Vietkong. Není však tak všeobecně známo, že řada Kennedyho poradců argumentovala proti tomuto navýšení a označila Vietnam za „slepou uličku“.

Tváří v tvář dvěma diametrálně odlišným cestám zvolil Kennedy, jak bylo pro jeho administrativu typické, střední cestu: místo masivního angažmá nebo úplného stažení se Kennedy raději rozhodl usilovat o omezené urovnání, vyslání pomoci, ale ne velkého počtu vojáků, což byla cesta, která byla od počátku téměř odsouzena k zániku. Řadou strategických omylů, včetně nuceného přesídlování venkovských vesničanů (tzv. strategický program Hamlet), byla Diemova podpora tak hluboce oslabena, že Kennedy váhavě a nejistě podpořil převrat, během něhož byl Diem zabit. O tři týdny později byl Kennedy rovněž zavražděn, což vyvolalo zmatek i na domácí politické scéně USA. Ironií osudu je, že konflikt, který Kennedy zahájil, bude ve skutečnosti později nejvíce spojován s jeho nástupcem.

Johnsonova válka

V době atentátu na Kennedyho působilo ve Vietnamu 16 000 amerických poradců, z nichž většina nebyla zapojena do každodenních bojových operací. Kennedyho viceprezident a jeho nový nástupce Lyndon Baines Johnson (LBJ) však nebyl přesvědčen, že tato cesta vede k úspěchu, a dospěl k názoru, že je třeba jednat agresivněji. Johnson využil pochybného incidentu, při němž vietnamské hlídkové čluny zaútočily na americké torpédoborce1 v Tonkinském zálivu, a využil proválečných nálad v Kongresu ke schválení rezoluce, která mu dávala pravomoc provést vojenskou akci bez výslovného vyhlášení války. Jednoduše řečeno, Johnson chtěl tuto válku vést „chladnokrevně“: „To znamenalo, že Amerika bude válčit ve Vietnamu s přesností chirurga a s malým dopadem na domácí kulturu. Omezená válka si vyžádala omezenou mobilizaci zdrojů, materiálních i lidských, a způsobila jen malé narušení každodenního života v Americe.“ (zdroj) V podstatě by se jednalo o válku, jejíž dopad by pocítili pouze Vietnamci - americký život a společnost by pokračovaly bez jakéhokoli povšimnutí událostí ve Vietnamu, a Johnson by se tak mohl věnovat své první velké lásce, své „Velké společnosti“, domácímu programu, který měl napravit mnoho neduhů americké společnosti, například chudobu2. Historie to samozřejmě ví lépe a - možná krutě - nazývá vietnamský konflikt „Johnsonovou válkou“.

Na úvod je třeba poznamenat, že Vietnam jako katastrofa je vnímán až v poslední době; Američané byli v průzkumech veřejného mínění ještě v roce 1967 přesvědčeni, že válka je dobrá věc, že je třeba zastavit komunismus a že Vietnam, pokud padne, bude prvním z řady států, které podlehnou komunistickému rozvratu. Tato „teorie domina“ byla běžným refrénem americké politiky druhé poloviny 20. století. Obavy tohoto druhu sužovaly americkou zahraniční politiku od doby, kdy komunisté úspěšně nebo téměř úspěšně rozvrátili několik evropských vlád v druhé polovině 40. let a poté Čínu v 50. letech. (Je třeba poznamenat, že Eisenhower a John Foster Dulles, kteří formulovali tuto teorii, nikdy nezařadili Vietnam do svého kruhu domina, které je třeba zachovat, a ve skutečnosti byl Eisenhower během některých svých setkání s Kennedym při přechodu do Bílého domu vůči Vietnamu překvapivě apatický.)

V roce 1968 se však situace ve Vietnamu výrazně změnila, protože Severovietnamci a Vietkong zahájili ofenzívu Tet, kampaň, která zmařila veškeré ujišťování americké vlády, že ve válce ve Vietnamu vítězí. Paradoxně, stejně jako po většinu války, ztratily síly NVA/VC značný počet vojáků, mnohem více než jejich američtí protivníci, přesto je ofenzíva Tet historiky všeobecně považována za bod zlomu americké vůle ve válce. Po této události se veřejné mínění obrátilo proti Johnsonovi a ten na dramatické tiskové konferenci oznámil, že nebude usilovat o své znovuzvolení. Dále oznámil, že bude usilovat o vyjednávání s Vietnamci.

Nixonův slib

Naneštěstí byla americká vyjednávací pozice vážně oslabena právě těmi protesty, které Američany k jednacímu stolu vůbec přivedly; vedení NVA/VC uznalo, že síly NVA/VC, navzdory ohromujícím vojenským ztrátám, které je téměř zlomily (několikrát), mohou jednoduše pokračovat v tom, co dělaly, a vymáhat od Američanů ústupky, aniž by nabídly nějaké na oplátku. Johnsonův nástupce, republikán Richard Nixon, kandidující s programem, který se skládal převážně ze slibu „dostat Ameriku z Vietnamu“, se pokusil o několik taktik, jak vyvinout tlak na síly NVA/VC, aby vyjednávaly, včetně zvýšené přítomnosti vzdušných sil (například vánoční bombardování a operace Menu) a pravidelného narušování nedalekého Laosu a Kambodže, sledování linie zásobování ze Severního Vietnamu k buňkám v Jižním Vietnamu. Nic však nepomohlo a v roce 1973 Nixonova vláda podepsala Pařížskou mírovou dohodu, která ukončila americkou účast v tomto konfliktu. O dva roky později byl jižní Vietnam ovládnut a 30. dubna 1975 se komunistické síly zmocnily Saigonu, hlavního města Vietnamu, což si vynutilo evakuaci amerického velvyslanectví a nejpamátnější obraz války - proudy prchajících lidí, kteří hledali místo ve vrtulníku Huey umístěném na střeše velvyslanectví.

Konec války

Druhá válka v jižní Indočíně skončila, Amerika zažila svou nejhlubší porážku v historii a Vietnam se stal synonymem pro „šlamastyku“. Její dopad na americkou kulturu byl nezměrný, neboť naučila celou generaci Američanů bát se své vlády a nedůvěřovat jí, naučila americké vůdce obávat se jakéhokoli množství amerických vojenských obětí a přinesla slovní spojení „jasná strategie odchodu“ přímo do amerického politického slovníku. Teprve když Ronald Reagan použil americkou armádu k „osvobození“ malého ostrovního státu Grenada, začali američtí prezidenti považovat americkou vojenskou intervenci za možný nástroj diplomacie, a i tehdy jen s velkým citem pro domácí obavy, jak zjistil Bill Clinton během svých mírových misí v Somálsku a Kosovu. Také z vyčíslitelného hlediska dopady Vietnamu zjevně nedosáhly Johnsonova cíle „chladnokrevné války“. Konečný součet: Ve válce sloužily 3 miliony Američanů, 150 000 jich bylo vážně zraněno, 58 000 mrtvých a více než 1 000 nezvěstných, nemluvě o téměř milionu obětí z řad NVA/Vietkongu, 250 000 jihovietnamských obětí a statisících - ne-li milionech, jak tvrdí někteří historici - civilních obětí.

Poučení z Vietnamu

Vietnam představuje pro studenty vojenských a politických dějin zajímavý problém - co přesně se pokazilo, kdy a kde? Je zřejmé, že neochota americké vlády přiznat svá selhání během války z ní činí snadného obětního beránka, ale žádná vláda v dějinách moderní společnosti nikdy nebyla vůči svému obyvatelstvu zcela upřímná, pokud jde o její válečné štěstí; jedním z takových příkladů je (ale nejen) pečlivá cenzura činnosti téže americké vlády během druhé světové války o padesát let dříve, známé v americké historii jako „poslední ‚dobrá‘ válka“. Je také lákavé poukázat na absenci vojenského cíle jako na zásadní selhání Vietnamu, ale jiné nevojenské cíle byly úspěšně realizovány vládami USA i jiných zemí, aniž by došlo k takovému kolosálnímu selhání, které provází vietnamský příběh. Navíc je důležité poznamenat, že USA měly ve skutečnosti jasný cíl v tom, co chtěly z konfliktu v jižní Indočíně vytěžit: zastavit pád jihovietnamské vlády, a pokud se tak nestane, zastavit „šíření“ komunismu. Byla to neochota americké vlády nasadit armádu naplno, jak vždy tvrdil generál William Westmoreland? Jistěže neúspěch ve Vietnamu nebyl vojenský; počty obětí jasně ukazují, že USA podle jakýchkoli jiných měřítek jasně vítězily.

Jaké byly tedy hlavní neúspěchy ve Vietnamu? A co je důležitější, co to všechno má společného s O/R mapováním?

Vietnam a O/R mapování

V případě Vietnamu se politický a vojenský aparát Spojených států potýkal se smrtelnou formou zákona snižujícího se mezního produktu. V případě automatizovaného objektově-relačního mapování jde o stejnou obavu - že počáteční úspěchy přinesou závazek používat O/R na místech, kde se úspěch stává nepolapitelnějším a časem není vůbec úspěšný kvůli časové a energetické režii potřebné k jeho podpoře ve všech možných případech použití. V podstatě největším poučením z Vietnamu - pro jakoukoli politickou či jinou skupinu - je vědět, kdy „sbalit fidlátka a zmizet“. Příliš často, jako tomu bylo ve Vietnamu, je snadné ospravedlnit další investice do určitého postupu naznačováním, že opuštění tohoto postupu nějakým způsobem znehodnocuje veškerou práci - nebo v případě Vietnamu životy amerických vojáků -, které již byly zaplaceny. Fráze jako „Došli jsme tak daleko, jistě to dotáhneme do konce“ a „Vycouvat teď znamená zahodit vše, co jsme doposud obětovali“ se stávají běžnými. Přinejmenším v pozdějších, hluboce rozhořčených letech druhé poloviny Vietnamské války přišly na přetřes otázky vlastenectví: pokud jste válku nepodporovali, byli jste zjevně zrádce, komunista, zjevně „neameričan“, neuctivý ke všem americkým veteránům jakékoli války vedené na jakémkoli území, ať už z jakéhokoli důvodu, a pravděpodobně jste kopali do svého psa. (Protestujícím nepomohlo ani to, že z války obviňovali vojáky a činili je odpovědnými - někdy i osobně - za rozhodnutí vojenských a politických vůdců, z nichž většinu vojáci ani protestující nikdy nepotkali).

Při vědomí toho, že všechny analogie nakonec selžou a že téma Vietnamu je hlubší, než může tato esej zkoumat, lze se zde přesto poučit ze zcela jiné oblasti. Jedním z klíčových poučení z Vietnamu bylo nebezpečí toho, čemu se hovorově říká „šikmá plocha“: že určitý postup může přinést určitý počáteční úspěch, avšak další investice do tohoto postupu přináší stále méně úměrné výsledky a stále nebezpečnější překážky, jejichž jediným řešením se zdá být větší a větší nasazení zdrojů a/nebo opatření. Někteří to nazývají „lékovou pastí“, podle způsobu, jakým mohou léky (legální nebo nelegální drogy) po delším užívání vykazovat snížený účinek a vyžadovat zvýšené dávkování, aby přinesly stejné výsledky. Jiní tomu říkají „problém poslední míle“: s blížícím se koncem problému je z hlediska nákladů (peněžních i abstraktních) stále obtížnější najít stoprocentní řešení. Všichni v podstatě hovoří o tomtéž - o obtížnosti nalezení odpovědi, která by našemu hrdinovi umožnila „dokončit“ daný problém úplně a uspokojivě.

Analýzu objektově-relačního mapování - a jeho vztahu k druhé válce v Jižní Indočíně - začneme především zkoumáním důvodů pro jeho vznik. Co vede vývojáře k tomu, že nepoužívají tradiční relační nástroje pro přístup k relační databázi a místo toho dávají přednost nástrojům, jako je ORM?

Nesoulad objektově-relačních impedancí

Říci, že objekty a relační datové sady jsou nějakým způsobem konstruovány odlišně, obvykle nepřekvapí žádného vývojáře, který někdy používal obojí; s výjimkou extrémně zjednodušených situací je poměrně zřejmé, že způsob, jakým je navrženo relační datové úložiště, se jemně - a přesto hluboce - liší od způsobu, jakým je navržen objektový systém.

Objektové systémy jsou obvykle charakterizovány čtyřmi základními komponentami: identita, stav, chování a zapouzdření.

  • Stav je celkem srozumitelný pojem, který se nejvíce blíží pojmu „data“.
  • Identita je ve většině objektově orientovaných jazyků implicitním pojmem v tom smyslu, že daný objekt má jedinečnou identitu, která je odlišná od jeho stavu (hodnoty jeho vnitřních proměnných) - dva objekty se stejným stavem jsou stále samostatné a odlišné objekty, přestože jsou bitově zrcadlovými obrazy jeden druhého. Jedná se o diskusi „identita vs. ekvivalence“, která se objevuje v jazycích jako C++, C# nebo Java, kde vývojáři musí rozlišovat mezi a == b a a.equals(b).
  • Chování objektu je poměrně snadno viditelné, jedná se o soubor operací, které mohou klienti vyvolat, aby nějakým způsobem manipulovali s objekty, zkoumali je nebo s nimi interagovali. (Tím se objekty liší od pasivních datových struktur v procedurálních jazycích, jako je C.)
  • Zapouzdření je klíčovým detailem, který zabraňuje vnějším stranám manipulovat s vnitřními detaily objektu, a poskytuje tak klientům evoluční schopnosti rozhraní objektu.3

Z toho můžeme odvodit další zajímavé koncepty, jako je typ, formální deklarace stavu a chování objektu, asociace, která umožňuje, aby se typy na sebe navzájem odkazovaly prostřednictvím odlehčeného odkazu namísto úplného vlastnictví podle hodnoty (někdy se nazývá kompozice), dědičnost, schopnost propojit jeden typ s jiným tak, že propojený typ zahrnuje veškerý stav a chování propojeného typu jako součást svého vlastního, a polymorfismus, schopnost nahradit objekt tam, kde se očekává jiný typ.

Relační systémy popisují formu ukládání a vyhledávání znalostí založenou na predikátové logice a pravdivostních výrocích. V podstatě každý řádek v tabulce je prohlášení o skutečnosti ve světě a jazyk SQL umožňuje operátově-efektivní vyhledávání dat těchto skutečností pomocí predikátové logiky k vytváření závěrů z těchto skutečností. [Date04] a [Fussell] definují relační model jako charakterizovaný vztahem, atributem, tuplem, hodnotou vztahu a proměnnou vztahu. Relace je ve své podstatě pravdivostní predikát o světě, výkaz faktů (atributů), které predikátu dodávají význam. Můžeme například definovat relaci „OSOBA“ jako {SSN, Jméno, Město}, která říká, že „existuje OSOBA s číslem sociálního pojištění SSN, která žije ve městě a jmenuje se Jméno“. Všimněte si, že v relaci je pořadí atributů zcela nespecifikované. Tuple je pravdivostní výrok v kontextu relace, množina hodnot atributů, které odpovídají požadované množině atributů v relaci, například {PERSON SSN='123-45-6789' Name='Catherine Kennedy' City='Seattle'}. Všimněte si, že dva tuply jsou považovány za identické, pokud jsou identické i hodnoty jejich relací a atributů. (To je opět na rozdíl od objektového systému, ve kterém jsou dva objekty identické pouze tehdy, pokud mají stejný jedinečný identifikátor - obvykle paměťovou adresu.)

A hodnota relace je tedy kombinací relace a množiny tuplů, které této relaci odpovídají, a proměnná relace je stejně jako většina proměnných zástupcem dané relace, ale může v průběhu času měnit hodnotu. Proměnná relace Lidé tak může být zapsána jako držitel relace {Lidé}, a skládat se z hodnoty relace

{ {PERSON SSN='123-45-6789" Jméno="Catherine Kennedy' Město="Seattle"},
  {PERSON SSN='321-54-9876' Jméno='Charlotte Neward' City='Redmond'},
  {PERSON SSN='213-45-6978' Jméno='Cathi Gero' City='Redmond'}. }

Tyto pojmy se běžně označují jako tabulky (proměnná vztahu), řádky (tuply), sloupce (atributy) a kolekce proměnných vztahu jako databáze. Tyto základní typy prvků lze vzájemně kombinovat pomocí sady operátorů (podrobně popsaných v kapitole 7 v [Date04]): restrict, project, product, join, divide, union, intersection a difference, a ty tvoří základ formátu a přístupu k SQL, všeobecně uznávanému jazyku pro interakci s relačním systémem z operátorských konzolí nebo programovacích jazyků. Použití těchto operátorů umožňuje vytvářet odvozené hodnoty relací, relace, které jsou vypočteny z jiných hodnot relací v databázi - například můžeme vytvořit hodnotu relace, která demonstruje počet lidí žijících v jednotlivých městech využitím operátorů project a restrict napříč výše definovanou proměnnou relace Lidé.

Již nyní je poměrně jasně vidět, že existují výrazné rozdíly mezi tím, jak relační a objektový svět nahlíží na „správný“ návrh systému, a postupem času se ukáží další. Je však důležité poznamenat, že dokud budou programátoři pro přístup k relačním datovým skladům preferovat objektově orientované programovací jazyky, bude vždy docházet k určitému druhu objektově-relačního mapování - oba modely jsou prostě příliš odlišné na to, aby je bylo možné tiše překlenout. (Pravděpodobně totéž platí pro objektově orientované a procedurální programování, ale to je jiný argument na jindy.)

ORM může probíhat v různých formách, z nichž nejjednodušší je rozpoznat automatizovaný nástroj pro ORM, například TopLink, Hibernate / NHibernate nebo Gentle.NET. Další formou mapování je ruční mapování, při kterém programátoři používají relačně orientované nástroje, jako je JDBC nebo ADO.NET, pro přístup k relačním datům a jejich „ruční“ extrakci do podoby příjemnější pro objektově orientované vývojáře. Třetí možností je jednoduše přijmout tvar relačních dat jako „ten“ model, z něhož se vychází, a objekty kolem něj tomuto přístupu podřídit; tento přístup je v lexikonu vzorů znám také jako Table Data Gateway [PEAA, 144] nebo Row Data Gateway [PEAA 152]; mnoho vrstev pro přístup k datům v Javě i .NET tento přístup využívá a kombinuje ho s generováním kódu, aby se zjednodušil vývoj této vrstvy. Někdy kolem relačního/tabulkového modelu postavíme objekty, kolem nich umístíme nějaké další chování a nazveme je Active Record [PEAA, 160].

Po pravdě řečeno, tento základní přístup - zotročení jednoho modelu podmínkami a přístupem druhého - byl tradiční odpovědí na nesoulad impedancí, která efektivně „řeší“ problém ignorováním jeho jedné poloviny. Bohužel, většina vývojových snah, podobně jako Kennedyho administrativa, není ochotna dotáhnout to do logického konce s plošným upřednostněním jednoho přístupu před druhým. Například zatímco většina vývojových týmů by ráda přijala přístup pouze „objektový“, na úrovni úložiště to předpokládá použití objektově orientovaného systému správy databází (OODBMS), což je téma, které často nemá žádnou odezvu ve vyšším managementu nebo v týmu správy podnikových dat. Opačný přístup - přístup „pouze relační“ - je vzhledem k tehdejším technologiím v době vzniku tohoto textu4 téměř nesmyslné zvažovat.

Vzhledem k tomu, že tedy není možné „uvolnit objekty na maximum“, jak by to nazval generál Westmoreland, zbývá nám nějaký hybridní přístup mapování objektů na relační, nejlépe takový, který je co nejvíce automatizovaný, aby se vývojáři mohli soustředit na svůj doménový model, a ne na detaily mapování objektů na tabulky. A tady bohužel začíná potenciální šlamastyka.

Problém mapování objektů na tabulky

Jedním z prvních a nejsnáze rozpoznatelných problémů při používání objektů jako front-end k relačnímu datovému skladu je problém, jak mapovat třídy na tabulky. Na první pohled se zdá, že jde o poměrně jednoduché cvičení - tabulky se mapují na typy, sloupce na instanční proměnné. Dokonce se zdá, že typy proměnných přímo odpovídají relačním typům sloupců, přinejmenším v poměrně izomorfní míře: VARCHAR na String, INTEGER na int a tak dále. Je tedy logické, že pro každou třídu definovanou v systému je definována odpovídající tabulka - pravděpodobně se stejným nebo příbuzným názvem -, která ji bude doprovázet. Nebo třeba, pokud se objektový kód zapisuje do již existujícího schématu, pak se třída mapuje na tabulku.

Je však přirozené, že s postupem času se dobře vyškolený objektově orientovaný vývojář bude snažit využít dědičnost v objektovém systému a hledat způsoby, jak totéž provést v relačním modelu. Relační model bohužel nepodporuje žádný druh polymorfismu ani vztah typu IS-A, a tak se vývojáři nakonec ocitnou v situaci, kdy přijmou jednu ze tří možných variant mapování dědičnosti do relačního světa: table-per-class, table-per-concrete-class nebo table-per-class-family. Každá z nich s sebou nese potenciálně významné nevýhody.

Přístup table-per-class je asi nejsnáze pochopitelný, protože se snaží minimalizovat „vzdálenost“ mezi objektovým a relačním modelem; každá třída v hierarchii dědičnosti dostane svou vlastní relační tabulku a objekty odvozených typů jsou sešity z relačních JOINů napříč různými tabulkami založenými na dědičnosti. Pokud má tedy například objektový model základní třídu OSOBA, od ní odvozenou třídu STUDENT a od ní odvozenou třídu ABSOLVENT, pak budou k uložení tohoto modelu zapotřebí tři tabulky: OSOBA, STUDENT a ABSOLVENT, z nichž každá bude obsahovat proměnné odpovídající stejnojmenné třídě. Vztah těchto tabulek k sobě však vyžaduje, aby každá z nich měla nezávislý primární klíč (takový, jehož hodnota není ve skutečnosti uložena v entitě objektu), aby každá odvozená třída mohla mít vztah cizího klíče k tabulce své nadtřídy. Důvod je jasný: objekt Absolvent je na základě svého vztahu IS-A ke třídám Student a Person kolekcí všech tří sad stavů a rozdíl mezi třídami je v okamžiku vytvoření objektu tohoto typu do značné míry odstraněn - například v Javě i .NET je samotný objekt kusem paměti, který obsahuje instanční proměnné definovaná ve všech jeho třídách a nadtřídách spolu s pointrem na tabulku metod definovaných stejnou hierarchií. To znamená, že při dotazování na konkrétní instanci na relační úrovni je třeba provést nejméně tři JOINy, aby se do pracovní paměti objektového programu dostal veškerý stav objektu.

Vlastně je to ještě horší - pokud hierarchie objektů dále roste, řekněme tak, že zahrnuje třídy Professor, Staff, Undergrad (dědí od třídy Student) a celou hierarchii AdjunctEmployees (dědí od třídy Staff), a program chce najít všechny Persons, jejichž příjmení je Smith, pak je třeba provést JOINy pro každou odvozenou třídu v systému, protože sémantika „najít všechny osoby“ znamená, že dotaz musí vyhledat data v tabulce PERSON, ale pak provést nákladnou sadu spojení JOIN, aby získal zbytek dat z celého zbytku databáze, přičemž pro získání zbytku dat je třeba vytáhnout tabulku PROFESSOR, nemluvě o tabulkách UNDERGRAD, ADJUCTEMPLOYEE, STAFF a dalších. Vzhledem k tomu, že JOINy patří mezi nejdražší výrazy v dotazech RDBMS, je zřejmé, že se do toho nebudete brát na lehkou váhu.

V důsledku toho vývojáři obvykle volí jeden ze dvou dalších přístupů, výhledově složitějších, ale efektivnějších při práci s relačním úložištěm: buď vytvoří tabulku pro každou konkrétní (nejvíce odvozenou) třídu a raději přijmou denormalizaci a její náklady, nebo vytvoří jedinou tabulku pro celou hierarchii, přičemž často v obou případech vytvoří diskriminační sloupec, který označuje, do které třídy patří každý řádek v tabulce. (Možné jsou i různé hybridy těchto schémat, ale obvykle nevytvářejí výsledky, které by se výrazně lišily od těchto dvou). Bohužel náklady na denormalizaci jsou u velkého objemu dat často značné a/nebo tabulka (tabulky) bude obsahovat značné množství prázdných sloupců, které budou potřebovat omezení NULLability na všechny sloupce, což eliminuje výkonná omezení integrity, která nabízí RDBMS.

Mapováním dědičnosti to nekončí; asociace mezi objekty, typické asociace s kardinalitou 1:n nebo m:n, tak běžně používané v SQL a/nebo UML, se řeší zcela jinak: V objektových systémech je asociace jednosměrná, od asociátora k asociovanému objektu (což znamená, že asociované objekty netuší, že jsou ve skutečnosti asociovány, pokud není vytvořena explicitní obousměrná asociace), zatímco v relačních systémech je asociace ve skutečnosti obrácená, od asociovaného objektu k asociátorovi (prostřednictvím sloupců cizích klíčů). To se ukazuje jako překvapivě důležité, protože to znamená, že pro asociace m:n musí být použita třetí tabulka pro uložení skutečného vztahu mezi asociátorem a asociovaným a dokonce i pro jednodušší vztahy 1:n nemá asociátor žádnou vlastní znalost vztahů, ke kterým se asociuje - zjištění těchto údajů vyžaduje v určitém okamžiku JOIN proti některé nebo všem asociovaným tabulkám. (Kdy tato data skutečně získat, je předmětem diskuse - viz níže Paradox načítání).

Konflikt mezi schématem a vlastnictvím

Diskuse o schématech mapování dědičnosti na tabulky a asociací také odhaluje základní chybu: V jádru mnoho nástrojů pro objektově-relační mapování předpokládá, že schéma je něco, co lze definovat podle schémat, která pomáhají optimalizovat dotazy ORM vůči relačním datům. To však popírá základní problém, že samotné databázové schéma často není pod přímou kontrolou vývojářů, ale je vlastněno jinou skupinou ve firmě, typicky skupinou správy databáze (DBA). Komu náleží odpovědnost za návrh databáze - a rozhodování o tom, kdy jsou změny schématu přípustné?

V mnoha případech začínají vývojáři nový projekt na „zelené louce“, s prázdnou relační databází, jejíž schéma mohou definovat podle svého uvážení. Brzy po dokončení projektu (někdy i dříve, kvůli politickým problémům a/nebo „válce o území“) se však ukáže, že vlastnictví schématu vývojáři je přinejlepším dočasné - různá oddělení se začnou dožadovat reportů k databázi, DBA se zodpovídají za výkonnost databáze, což jim dává důvod volat po „refaktorování“ a denormalizaci dat, a ostatní vývojové týmy se mohou začít zajímat o to, jak by mohly využít data v ní uložená. Zanedlouho musí být schéma „zmrazeno“, čímž se potenciálně vytvoří překážka pro refaktorování objektového modelu. Kromě toho budou tyto další týmy očekávat relační model definovaný v relačních termínech, nikoliv takový, který podporuje zcela ortogonální formu perzistence - například sloupec „diskriminátor“ z problému mapování dědičnosti na tabulky bude pro relační generátory sestav, jako je Crystal Reports, představovat obtíže a pravděpodobně bude zcela nepoužitelný. Pokud nejsou vývojáři ochotni psát všechny sestavy (a jejich uživatelská rozhraní, a jejich tiskový kód, a jejich ad-hoc možnosti…) ručně, bude to obvykle nepřijatelný stav.

(Abychom byli spravedliví, nejedná se ani tak o technický problém, jako spíše o problém politický, ale stále představuje vážný problém bez ohledu na jeho zdroj - nebo řešení. A jako takový stále představuje překážku pro řešení objektově-relačního mapování.”

Problém dvojího schématu

S otázkou vlastnictví schématu souvisí i to, že v řešení ORM jsou metadata k systému uložena zásadně na dvou různých místech: jednou ve schématu databáze a jednou v objektovém modelu (chcete-li, dalším schématu vyjádřeném v jazyce Java nebo C# namísto DDL). Aktualizace nebo refaktorování jednoho z nich bude pravděpodobně vyžadovat podobné úpravy druhého. Refaktorování kódu tak, aby odpovídal změnám schématu databáze, je obecně považována za jednodušší - refaktorování databáze často vyžaduje určitý druh migrace a/nebo úpravy dat již v databázi, kdežto u kódu takový požadavek není. (Objekty, alespoň v této diskusi, jsou efemérní instance v paměti, které zmizí, jakmile proces, který je drží, skončí. Pokud jsou objekty uloženy v nějakém druhu objektové formy, která může přetrvat napříč prováděním procesu - například serializované instance objektů uložené na disku - pak se refaktorování objektů stává stejně problematickou.)

Důležitější je, že ačkoli není neobvyklé, aby byl kód nasazen specificky pro jednu aplikaci, často jsou instance databáze využívány více než jednou aplikací a pro firmu je často nepřijatelné vyvolat celofiremní refaktorování kódu jen proto, že refaktorování v jedné aplikaci vyžaduje podobné refaktorování řízené databází. V důsledku toho bude s postupným růstem systému narůstat tlak na vývojáře, aby „odpoutali“ objektový model od databázového schématu tak, aby změny schématu nevyžadovaly podobné refaktorování objektového modelu a naopak. V některých případech, kdy ORM takové odpojení neumožňuje, může být nutné nasadit zcela soukromou instanci databáze s přesným schématem, na kterém bylo postaveno řešení založené na ORM, čímž vznikne další datové silo v prostředí IT, kde sílí tlak na redukci takových sil.

Problémy s identitou entit

Jako by tyto problémy nestačily, narážíme na další problém, a to identitu objektů a vztahů. Jak bylo uvedeno výše, objektové systémy používají implicitní smysl pro identitu, obvykle založený na umístění objektu v paměti (všudypřítomný ukazatel this); alternativně se někdy označuje jako OID (Object IDentifier), obvykle v systémech, které přímo nevystavují umístění v paměti, jako je například objektová databáze (kde je ukazatel v paměti jako identifikátor mimo proces databáze celkem k ničemu). V relačním modelu je však identita implicitně obsažena v samotném stavu - dva řádky s naprosto stejným stavem jsou obvykle považovány za poškození relačních dat, protože dvakrát tvrzená stejná skutečnost je nadbytečná a kontraproduktivní. Abychom byli spravedliví, měli bychom zde být trochu explicitnější; relační systém může ve skutečnosti povolit duplicitní tuply (jak je popsáno výše), ale to je často explicitně zakázáno explicitními relačními omezeními, jako jsou omezení PRIMARY KEY. V situacích, kdy jsou duplicitní hodnoty povoleny, neexistuje pro relační systém žádný způsob, jak určit, který ze dvou duplicitních řádků je načítán - neexistuje žádný implicitní smysl pro identitu relace kromě toho, který nabízejí její atributy. Totéž neplatí pro objektové systémy, kde dva objekty, které obsahují přesně identické bitové vzory na dvou různých místech paměti, jsou ve skutečnosti samostatné objekty. (To je důvod, proč se v Javě nebo C# rozlišuje mezi == a .equals().) Důsledek je zde jednoduchý: pokud se mají oba systémy shodnout na smyslu identity, musí relační systém nabídnout nějaký jedinečný pojem identity (obvykle automaticky se zvyšující celočíselný sloupec), který bude odpovídat pojmu identity objektu.

To vyvolává vážné obavy, pokud jde o automatizované systémy OR, protože smysl identity je zcela odlišný - pokud dvě samostatné uživatelské relace interagují se stejným vztahem v úložišti, nastupují systémy souběžnosti relačního databázového systému a zajišťují určitou formu souběžného přístupu, obvykle prostřednictvím transakční metafory (ACID). Pokud systém O/R načte relaci z úložiště (v podstatě vytvoří „pohled“ na data), máme nyní druhý zdroj identity dat, jeden v databázi (chráněný výše zmíněným transakčním schématem) a jeden v objektové reprezentaci těchto dat v paměti, která nemá žádnou konzistentní transakční podporu kromě té, která je zabudována v jazyce (například koncept monitorů v jazycích Java a .NET) nebo knihovny (například System.Transactions v .NET 2.0), které mohou být - a bohužel často jsou - vývojáři snadno ignorovány. Správa izolace a souběžnosti (concurrency) není snadno řešitelný problém a bohužel jazyky a platformy běžně dostupné vývojářům zatím nejsou tak konzistentní a flexibilní jako metafora databázových transakcí.

Tento problém dále komplikuje skutečnost, že mnoho systémů O/R zavádí do vrstvy O/R významnou podporu cachování (obvykle ve snaze zvýšit výkon a vyhnout se obcházení databáze), což zase přináší určité problémy, zejména pokud systém cachování není cache pro zápis, kde dochází ke skutečnému zápisu databáze; a co to vypovídá o transakční integritě, pokud se aplikační kód domnívá, že k zápisu došlo, ačkoli se tak ve skutečnosti nestalo? Tento problém se zase jen prohlubuje, pokud systém O/R běží ve více procesech před databázovým strojem, což se běžně vyskytuje ve scénářích clusterových aplikačních serverů. Nyní je identita dat rozložena do n+1 míst, přičemž n je počet uzlů aplikačního serveru a 1 je samotná databáze. Každý uzel musí nějakým způsobem signalizovat svůj záměr provést aktualizaci ostatním uzlům, aby získal nějakou konstrukci souběhu, která zabrání současnému přístupu (jinou instancí stejné relace nebo instancí jiné relace přistupující ke stejným datům), což zabere čas a zabíjí výkon. Dokonce i v případě cache určené pouze pro čtení musí být aktualizace datového úložiště nějakým způsobem signalizovány cache běžící v uzlech aplikačního serveru, což vyžaduje komunikaci mezi serverem a klientem pocházející z databáze; podpora tohoto postupu není v současných moderních relačních databázích dobře pochopena ani zdokumentována.

Obavy z mechanismu získávání dat

Jakmile je entita uložena v databázi, jak ji přesně získáme? Čistě objektově orientovaný přístup by pro vyhledávání využíval objektové přístupy, v ideálním případě pomocí syntaxe ve stylu konstruktorů identifikujících požadované objekty, ale bohužel syntaxe konstruktorů není dostatečně obecná, aby umožnila něco tak flexibilního; zejména jí chybí možnost inicializovat kolekci objektů a dotazy často potřebují vrátit kolekci, nikoli pouze jednu entitu. (Vícenásobné cesty do databáze za účelem načtení jednotlivých entit jsou obecně považovány za příliš neekonomické, a to jak z hlediska latence, tak šířky pásma, než aby se daly považovat za věrohodnou alternativu - více viz níže Paradox času načítání.) Výsledkem je obvykle jeden z přístupů Query-By-Example (QBE), Query-By-API (QBA) nebo Query-By-Language (QBL).

Přístup QBE spočívá v tom, že vyplníte šablonu objektu typu objektu, který hledáte, přičemž instanční proměnné jsou nastaveny na konkrétní hodnotu, která se použije jako součást procesu filtrace dotazu. Pokud se tedy například dotazujete na objekt/tabulku Osoba na osoby s příjmením Smith, nastavíte dotaz takto:

Person p = new Person(); // předpokládá, že všechny instanční proměnné jsou ve výchozím nastavení nulové
p.lastName = "Smith";
ObjectCollection oc = QueryExecutor.execute(p);

Problém s přístupem QBE je zřejmý: zatímco pro jednoduché dotazy je naprosto dostačující, není zdaleka dostatečně expresivní pro podporu složitějšího stylu dotazů, které často potřebujeme provést - „najdi všechny osoby jménem Smith nebo Cromwell“ a „najdi všechny osoby, které se NEjmenují Smith“ jsou dva příklady. Ačkoli není nemožné vytvořit přístupy QBE, které si s tímto (a složitějšími scénáři) poradí, rozhodně to značně komplikuje rozhraní API. Ještě důležitější je, že to také nutí doménové objekty do nepříjemné pozice - musí podporovat nulovatelné instanční proměnné, což může být porušením doménových pravidel, která by se jinak objekt snažil podporovat - Osoba beze jména není v mnoha scénářích příliš užitečný objekt, ale právě to bude přístup QBE vyžadovat od doménových objektů v něm uložených. (Praktici QBE budou často tvrdit, že není nerozumné, aby implementace objektu toto zohledňovala, ale opět to není ani snadné, ani časté.)

V důsledku toho je obvykle druhým krokem, aby objektový systém podporoval přístup „Query-By-API“, v němž jsou dotazy konstruovány pomocí dotazovacích objektů, obvykle něco ve tvaru:

Query q = new Query();
q.From("PERSON").Where(new EqualsCriteria("PERSON.LAST_NAME", "Smith"));
ObjectCollection oc = QueryExecutor.execute(q);

Tady není dotaz založen na prázdné „šabloně“ objektu, který má být získán, ale na sadě „dotazovacích objektů“, které jsou společně použity k definování objektu ve stylu příkazu pro provedení proti databázi. Více kritérií je spojeno pomocí nějaké binomické konstrukce, obvykle objektů And a Or, z nichž každý obsahuje jedinečné objekty Criteria pro testování. Další objekty filtrace/manipulace lze označit na konci, obvykle připojením volání jako OrderBy(fieldName) nebo GroupBy(fieldName). V některých případech jsou tato volání metod vlastně objekty zkonstruované programátorem a explicitně zřetězené.

Vývojáři si rychle všimnou, že výše uvedený přístup je (obecně) mnohem více slovní než tradiční přístup SQL a některé styly dotazů (zejména netradiční spojení, například vnější spojení) je mnohem obtížnější - ne-li nemožné - reprezentovat v přístupu QBA.

Kromě toho tu máme ještě jeden subtilnější problém, a to závislost na disciplíně vývojářů: jak název tabulky PERSON, tak název sloupce v kritériích PERSON.LAST_NAME jsou standardní řetězce, které se přebírají tak, jak jsou, a do systému se vkládají za běhu bez jakékoliv kontroly platnosti. To představuje klasický problém v programování, chybu překlepu, kdy se vývojář ve skutečnosti nedotazuje na tabulku PERSON, ale na tabulku PRESON. Rychlý unit-test proti živé instanci databáze sice chybu odhalí, ale to předpokládá dvě skutečnosti - že vývojáři jsou zarytí do testování a že unit-testy jsou prováděny proti instancím databáze. Zatímco první z těchto skutečností se pomalu stává zárukou, protože stále více vývojářů je „nakaženo testováním“ (vypůjčím si terminologii Gama a Becka), druhá skutečnost je stále zcela otevřená diskusi a interpretaci vzhledem k tomu, že vhodné nastavení a rozpojení instance databáze pro unit testy je v databázi stále obtížně proveditelné. (Ačkoli existuje řada způsobů, jak tento problém obejít, zdá se, že se jich používá jen málo.)

Setkáváme se také se základním problémem, že je od vývojáře vyžadováno větší povědomí o logické - nebo fyzické - reprezentaci dat - místo toho, aby se vývojář jednoduše soustředil na to, jak spolu objekty souvisejí (prostřednictvím jednoduchých asociací, jako jsou pole nebo instance kolekcí), musí mít nyní větší povědomí o formě, v níž jsou objekty uloženy, což činí systém poněkud zranitelným vůči změnám schématu databáze. Tomuto problému se někdy vyhýbá hybridní přístup mezi těmito dvěma přístupy, kdy systém převezme odpovědnost za interpretaci asociací a ponechá na vývojáři, aby napsal něco takového:

Query q = new Query();
Field lastNameFieldFromPerson = Person.class.getDeclaredField("lastName");
q.From(Person.class).Where(new EqualsCriteria(lastNameFieldFromPerson, "Smith"));
ObjectCollection oc = QueryExecutor.execute(q);

Což částečně řeší problém s uvědoměním si schématu a problém překlepu, ale stále ponechává vývojáře zranitelného vůči obavám ze slovíčkaření a stále neřeší složitost sestavení složitějšího dotazu, například dotazu na více tabulek (nebo chcete-li více tříd) spojených na základě několika kritérií různými způsoby.

Dalším úkolem je tedy vytvořit přístup „Query-By-Language“, v němž je napsán nový jazyk, podobný jazyku SQL, ale nějakým způsobem „lepší“, který podporuje složité a výkonné dotazy běžně podporované jazykem SQL; dva příklady takového jazyka jsou OQL a HQL. Problémem je, že tyto jazyky jsou často podmnožinou jazyka SQL, a proto nenabízejí plnou sílu jazyka SQL. Ještě důležitější je, že vrstva O/R nyní ztratila důležitý „prodejní argument“, a to mantru „objekty a pouze objekty“, která ji zplodila; používat jazyk podobný SQL je téměř stejné jako používat samotné SQL, takže jak může být více „objektový“? Vývojáři sice nemusí znát fyzické schéma datového modelu (o mapování, o kterém jsme hovořili dříve, se může postarat interpret/executor dotazovacího jazyka), ale budou si muset být vědomi toho, jak jsou v jazyce reprezentovány asociace a vlastnosti objektů a jaká je podmnožina schopností objektů v rámci dotazovacího jazyka - je například možné napsat něco takového?

SELECT Person p1, Person p2
FROM Person
WHERE p1.getSpouse() == null 
  AND p2.getSpouse() == null 
  AND p1.isThisAnAcceptableSpouse(p2) 
  AND p2.isThisAnAcceptableSpouse(p1);

Jinými slovy, prohledejte databázi a najděte všechny svobodné osoby, které se navzájem považují za přijatelné. Zatímco metoda isThisAnAcceptableSpouse je zjevně metodou, která patří do třídy Person (každá instance Person může mít svá vlastní kritéria, podle kterých posuzuje přijatelnost jiného svobodného - zda je to blondýna, bruneta nebo zrzka, zda vydělává více než 100 tisíc dolarů ročně a tak dále), není jasné, zda je provedení této metody možné v dotazovacím jazyce, a není ani jasné, zda by mělo být. I u těch nejtriviálnějších implementací dojde pravděpodobně k vážnému zásahu do výkonu, zejména pokud musí vrstva O/R přeměnit data relačních sloupců na objekty, aby bylo možné dotaz provést. Navíc nemáme žádnou záruku, že vývojář napsal tuto metodu tak, aby byla vůbec efektivní, a nemáme žádný způsob, jak vynutit nějakou implementaci zohledňující výkon.

(Kritici budou tvrdit, že se jedná o řešitelný problém, a navrhnou dvě možná řešení. Jedním z nich je zakódovat preferenční údaje do samostatné tabulky a učinit ji součástí dotazu; výsledkem bude ohavně komplikovaný dotaz, který bude mít délku několika stránek a pravděpodobně bude vyžadovat experta na SQL, aby ho později rozplétal, když bude chtít přidat nová preferenční kritéria. Druhou možností je zakódovat tuto implementaci „přijatelnosti„“ do uložené procedury v rámci databáze, což nyní zcela odstraní kód z objektového modelu a ponechá nás bez jakéhokoli „objektového“ řešení - přijatelné, ale pouze pokud přijmete předpoklad, že ne všechna implementace může spočívat uvnitř samotného objektového modelu, což odmítá předpoklad „objekty a nic než objekty“, kterým mnozí zastánci O/R otevírají své argumenty.)

Problém částečných objektů a paradox času načítání

Je již dlouho známo, že procházení sítě, například při tradičním požadavku SQL, trvá značnou dobu. (Podle hrubých srovnávacích testů se tato hodnota pohybuje v rozmezí tří až pěti řádů ve srovnání s jednoduchým voláním metody na platformě Java nebo .NET5; zhruba analogicky, pokud vám ráno cesta do práce trvá dvacet minut a nazýváme ji dobou potřebnou k provedení volání místní metody, čtyři řády k této hodnotě představují zhruba dobu potřebnou k cestě na Pluto, neboli necelých čtrnáct let v jednom směru). Tyto náklady jsou zjevně netriviální, takže v důsledku toho vývojáři hledají způsoby, jak tyto náklady minimalizovat optimalizací počtu síťových komunikací a načítaných dat.

V jazyce SQL se této optimalizace dosahuje pečlivým strukturováním požadavku SQL, kdy se dbá na to, aby se načítaly pouze požadované sloupce a/nebo tabulky, nikoli celé tabulky nebo sady tabulek. Například při konstrukci pečlivě plánovaného tradičního uživatelského rozhranín vývojář předloží souhrnné zobrazení všech záznamů, z nichž si uživatel může vybrat jeden, a po výběru pak zobrazí kompletní sadu dat pro daný záznam. Zaměřme se na příklad relačního typu Persons popsaného dříve, dva dotazy k tomu určené by byly v tomto pořadí (za předpokladu, že je vybrán první z nich):

SELECT id, first_name, last_name FROM person;

SELECT * FROM osoba WHERE id = 1;

Zejména si všimněte, že v každé fázi procesu jsou získány pouze požadované údaje - v prvním dotazu potřebné souhrnné informace a identifikátor (pro následný dotaz v případě, že by k přímé identifikaci osoby nestačilo jméno a příjmení) a ve druhém zbytek údajů k zobrazení. Většina odborníků na SQL se ve skutečnosti vyhýbá syntaxi sloupců se zástupným znakem * a raději v dotazu každý sloupec pojmenuje, a to jak z důvodu výkonu, tak z důvodu údržby - z důvodu výkonu, protože databáze bude lépe optimalizovat dotaz, a z důvodu údržby, protože bude menší pravděpodobnost, že budou vráceny nepotřebné sloupce, jak se budou DBA nebo vývojáři vyvíjet a/nebo refaktorovat příslušné databázové tabulky. Toto pojetí možnosti vrátit část tabulky (i když stále v relační podobě, což je důležité z výše popsaných důvodů uzavřenosti) je zásadní pro možnost optimalizovat tyto dotazy tímto způsobem - většina dotazů bude ve skutečnosti vyžadovat pouze část kompletního vztahu.

To představuje problém pro většinu, ne-li všechny vrstvy mapování objektů a relací: cílem každé O/R je umožnit vývojáři vidět „nic než objekty“, a přesto vrstva O/R nemůže z jednoho požadavku na druhý říci, jak budou objekty vrácené dotazem použity. Je například zcela reálné, že většina vývojářů bude chtít napsat něco ve stylu:

Person[] all = QueryManager.execute(....);
Person selected = DisplayPersonsForSelection(all);
DisplayPersonData(selected);

Jinými slovy to znamená, že jakmile je z pole Person vybrána osoba, která má být zobrazena, není třeba provádět žádnou další akci načítání - koneckonců máte svůj objekt, co víc by mělo být potřeba?

Problém zde spočívá v tom, že data, která mají být zobrazena v prvním volání Display...(), nejsou celá osoba, ale podmnožina těchto dat; zde narážíme na první problém v tom, že objektově orientovaný systém, jako je C# nebo Java, nemůže vrátit pouze „části“ objektu - objekt je objekt, a pokud se objekt Person skládá z 12 instančních proměnných, pak bude všech 12 přítomno v každé vrácené osobě. To znamená, že systém stojí před jednou ze tří nepříjemných možností: za prvé, požadovat, aby objekty Person byly schopny obsahovat „nulovatelné“ proměnné, bez ohledu na doménová omezení, která tomu brání; za druhé, vrátit Person kompletně vyplněný všemi daty, která objekt tvoří; nebo za třetí, poskytnout nějaký druh načítání na vyžádání, které získá tyto instanční proměnné, pokud a kdy vývojář k těmto proměnným přistupuje, a to i nepřímo, třeba prostřednictvím volání metody.

(Všimněte si, že některé objektové jazyky, například ECMAScript, nahlížejí na objekty jinak než jazyky založené na třídách, například Java nebo C# či C++, a v důsledku toho je zcela možné vracet objekty, které obsahují různý počet instančních proměnných. Takový přístup však má jen málo jazyků, dokonce ani oblíbený dynamický jazyk Ruby, a dokud se takové jazyky nerozšíří, zůstává taková diskuse mimo oblast této eseje.)

Pro většinu O/R vrstev to znamená, že objekty a/nebo pole objektů musí být načítány líným způsobem, získáváním dat na vyžádání, protože načítání všech instančních proměnných všech objektů/vztahů osob by pro tento konkrétní scénář „zjevně“ znamenalo obrovské plýtvání šířkou pásma. Obvykle se celá sada instančních proměnných objektu načte při přístupu k jakékoli dosud nevrácené instanční proměnné. (Tento přístup se upřednostňuje před přístupem po jednotlivých proměnných, protože je zde menší pravděpodobnost „problému N+1 dotazů“, kdy načtení všech dat z objektu vyžaduje 1 dotaz na načtení primárního klíče + N dotazů na načtení každé proměnné z tabulky podle potřeby. Tím se minimalizuje šířka pásma spotřebovaná na načtení dat - žádná nezpracovaná proměnná nebude mít načtena svá data - ale zjevně se nepodaří minimalizovat síťové interakce.)

Naneštěstí jsou proměnné v rámci objektu jen částí problému - další problém, kterému čelíme, spočívá v tom, že objekty jsou často spojeny s jinými objekty, a to v různých kardinalitách (jeden k jednomu, jeden k mnoha, mnoho k jednomu, mnoho k mnoha), a mapování O/R musí předem rozhodnout, kdy tyto přidružené objekty načíst, a přes veškerou snahu vývojářů ORM budou vždy existovat běžné případy použití, kdy učiněné rozhodnutí bude přesně to špatné. Většina ORM nabízí nějakou vývojářem řízenou podporu rozhodování, obvykle nějaký konfigurační nebo mapovací soubor, který přesně určí, jaká bude politika načítání, ale toto nastavení je globální pro danou třídu a jako takové ho nelze situačně měnit.

Shrnutí

Pokud je tedy mapování objektů do relací v moderním firemním systému nutností, jak ho může někdo prohlásit za šlamastyku, ze které není úniku? Jako užitečná analogie zde opět slouží Vietnam - zatímco situace v jižní Indočíně vyžadovala reakci Američanů, Kennedyho a Johsonova administrativa měla k dispozici celou řadu reakcí, včetně stejné reakce, jakou vyvolal nedávný pád Suharta v Malajsii, tedy žádné. (Nezapomeňte, že Eisenhower a Dulles v první řadě nepovažovali jižní Indočínu za součást teorie domina; mnohem více je zajímalo Japonsko a Evropa.)

Nabízí se několik možných řešení problému ORM, z nichž některá vyžadují určitý druh „globální“ akce celé komunity, jiná jsou přístupnější vývojovým týmům „v zákopech“:

  • Opuštění Vývojáři se jednoduše zcela vzdají objektů a vrátí se k programovacímu modelu, který nevytváří nesoulad mezi objektovou a relační impedancí. I když je to nepříjemné, v určitých scénářích vytváří objektově orientovaný přístup více režie, než kolik ušetří, a návratnost investice jednoduše není taková, aby ospravedlnila náklady na vytvoření bohatého doménového modelu. ([Fowler] o tom hovoří poněkud do hloubky.) To tento problém poměrně elegantně odstraňuje, protože pokud neexistují žádné objekty, nedochází k impedančnímu nesouladu.
  • Celkové přijetí Vývojáři se jednoduše zcela vzdají relačního úložiště a používají model úložiště, který odpovídá způsobu, jakým se na svět dívají jejich zvolené jazyky. Systémy objektového ukládání, jako je například projekt db4o (který je bohužel od roku 2021 v podstatě nefunkční), řeší problém elegantně tím, že ukládají objekty přímo na disk, čímž odstraňují mnoho (ale ne všechny) z výše uvedených problémů; neexistuje například žádné “druhé schéma”, protože jediné použité schéma je schéma samotných definic objektů. Ačkoli mnozí DBA při této myšlence omdlí, ve světě stále více orientovaném na služby, který se vyhýbá myšlence přímého přístupu k datům, ale místo toho vyžaduje, aby veškerý přístup probíhal přes bránu služby, čímž je mechanismus ukládání zapouzdřen mimo dosah zvědavých očí, je zcela reálné si představit, že vývojáři ukládají data ve formě, která je pro ně mnohem snazší než pro DBA.
  • Ruční mapování Vývojáři se prostě smíří s tím, že to nakonec není tak těžký problém, aby ho řešili ručně, a napíší rovnou kód pro relační přístup, který bude vracet relace do jazyka, přistupovat k tuplům a naplňovat objekty podle potřeby. V mnoha případech může být tento kód dokonce automaticky generován nástrojem zkoumajícím metadata databáze, čímž se eliminuje některá z hlavních kritik tohoto přístupu (a to: „Je to příliš mnoho kódu na psaní a údržbu“).
  • Přijetí omezení ORM Vývojáři se jednoduše smíří s tím, že neexistuje způsob, jak efektivně a snadno uzavřít smyčku nesouladu O/R, a použijí ORM k vyřešení 80 % (nebo 50 % nebo 95 % nebo jakéhokoli procenta, které se jim zdá vhodné) problému a využijí SQL a přístup založený na relacích (například „surový“ JDBC nebo ADO.NET), aby se přenesli přes ty oblasti, kde by ORM způsoboval problémy. Takový postup však s sebou nese vlastní podíl rizik, protože vývojáři používající ORM si musí být vědomi jakéhokoli ukládání do cache, které v rámci něj řešení ORM provádí, protože „surový“ relační přístup zjevně nebude schopen tuto vrstvu ukládání do cache využít.
  • Integrace relačních konceptů do jazyků Vývojáři se prostě smíří s tím, že tento problém by měl řešit jazyk, nikoli knihovna nebo framework. V posledních deseti nebo více letech se důraz na řešení problému O/R soustředil na snahu přiblížit objekty databázi, aby se vývojáři mohli soustředit výhradně na programování v jediném paradigmatu (tímto paradigmatem jsou samozřejmě objekty). V posledních několika letech však zájem o „skriptovací“ jazyky s mnohem silnější podporou množin a seznamů, jako je Ruby, podnítil myšlenku, že je možná vhodné jiné řešení: vnést relační koncepty (které jsou ve své podstatě založeny na množinách) do hlavních programovacích jazyků, což usnadní překlenutí propasti mezi „množinami“ a „objekty“. Práce v této oblasti je zatím omezená a omezuje se většinou na výzkumné projekty a/nebo „okrajové“ jazyky, ale v komunitě se zviditelňuje několik zajímavých snah, jako jsou hybridní funkcionální/objektové jazyky, například Scala nebo F#, a také přímá integrace do tradičních O-O jazyků, jako je projekt LINQ společnosti Microsoft pro C# a Visual Basic. Jednou z takových snah, která bohužel selhala, byla strategie SQL/J; i tam byl přístup omezený, nesnažil se začlenit množiny do Javy, ale pouze umožnit, aby vložená volání SQL byla předzpracována a přeložena do kódu JDBC překladačem.
  • Integrace relačních konceptů do frameworků Vývojáři prostě uznávají, že tento problém je řešitelný, ale pouze se změnou perspektivy. Místo aby se spoléhali na to, že tento problém vyřeší návrháři jazyků nebo knihoven, zaujmou jiný pohled na „objekty“, který je více relační povahy, a vytvoří doménové rámce, které jsou více přímo postaveny na relačních konstrukcích. Například místo vytváření třídy Person, která uchovává svá instanční data přímo v proměnných uvnitř objektu, vytvářejí vývojáři třídu Person, která uchovává svá instanční data v instanci RowSet (Java) nebo DataSet (C#), kterou lze sestavit s dalšími RowSet/DataSet do snadno přenositelného bloku dat pro aktualizaci proti databázi nebo rozbalit z databáze do jednotlivých objektů.

Všimněte si, že tento seznam není uveden v žádném konkrétním pořadí; zatímco některé jsou atraktivnější než jiné, to, které jsou „lepší“, je hodnotový soud, který si musí každý vývojář a vývojový tým udělat sám.

Stejně jako je možné si představit, že USA mohly dosáhnout určitého „úspěchu“ ve Vietnamu, kdyby se držely jasné strategie a chápaly jasnější vztah mezi závazkem a výsledky (ROI, chcete-li), je možné si představit, že objektový/relační problém lze „vyhrát“ pečlivým a uvážlivým uplatňováním strategie, která si je celá vědoma svých vlastních omezení. Vývojáři musí být ochotni brát „výhry“ tam, kde je mohou získat, a nesklouznout na šikmou plochu tím, že se budou snažit vytvářet řešení, která budou stále dražší a méně výnosná. Bohužel, jak ukazuje historie Vietnamské války, ani vědomí nebezpečí šikmé plochy často nestačí k tomu, aby se zabránilo zabřednutí do bažiny. A co hůř, je to bažina, která je prostě příliš přitažlivá na to, aby se jí dalo vyhnout, píseň sirén, která stále přitahuje vývojové týmy všech velikostí korporací (včetně těch v Microsoftu, IBM, Oracle a Sunu, abychom jmenovali alespoň některé) ke skalám, a to s velkolepými výsledky. Přivažte se ke stěžni, pokud chcete píseň slyšet, ale nechte veslovat námořníky.

Poznámky na závěr

[1] Pozdější analýzy zúčastněných ředitelů - včetně tehdejšího ministra obrany Roberta McNamary - dospěly k závěru, že polovina útoku se vůbec neuskutečnila.

[2] Je možná největší ironií války, že muž, kterého si osud vybral do čela během největšího amerického zahraničního angažmá, byl vůdce, jehož hlavní pozornost byla zaměřena výhradně na vlastní břehy. Kdyby se okolnosti nespikly jinak, hippies skandující před Oválnou pracovnou “Hej, hej LBJ, kolik kluků jsi dneska zabil” by dost možná byli Johnsonovými nejvěrnějšími stoupenci.

[3] Ironií osudu se ukazuje, že zapouzdření je z důvodu jednoduchosti údržby hlavní motivací téměř všech významných inovací v lingvistické informatice - procesní, funkcionální, objektové, aspektové, dokonce i relační technologie ([Date02]) a další jazyky uvádějí „zapouzdření“ jako hlavní hnací faktor.

[4] Za „relační“ programovací jazyky bychom snad mohli považovat jazyky uložených procedur jako T-SQL nebo PL/SQL, ale i v takovém případě je nesmírně obtížné vytvořit uživatelské rozhraní v PL/SQL.

[5] V tomto případě jsem poměřoval volání metod RMI v Javě s voláním lokálních metod. Podobné výsledky lze celkem snadno získat pro přístup k datům na bázi SQL měřením volání mimo proces proti voláním v procesu pomocí databázového produktu, který podporuje obojí, například Cloudscape/Derby nebo HSQL (Hypersonic SQL).

Reference

[Fussell] Foundations of Object Relational Mapping, by Mark L. Fussell, v0.2 (mlf-970703)

[Fowler] Patterns of Enterprise Application Architecture, by Martin Fowler

[Date04] Introduction to Database Systems, 8th Edition, by Chris Date.