Přejít k navigační liště

Zdroják » PHP » O Doctrine 2 Best Practices

O Doctrine 2 Best Practices

Články Databáze, PHP

Jakub Kulhan dával na Twitteru odkaz na slajdy o nejlepší praktikách v Doctrine. Protože se o práci s databází zajímám, slajdy jsem si prošel. Některé praktiky mi přijdou dobré, některé věci bych řešil jinak. Určitě si projděte i ty slajdy, já reaguju jen na to, co mě dovedlo k zamyšlení.

Nálepky:

Článek původně vyšel na autorově blogu.

Jiří Knesl už téměř 8 let zlepšuje organizaci práce v IT firmách a IT týmech. Při své práci používá řadu nástrojů, jako je agile, Scrum, Lean, Theory of Constraints a další. Více o něm najdete na www.knesl.com.

Můj přístup je hodně ovlivněný tím, že dělám primárně v Clojure, že používám Postgres a že se snažím být efektivní.

OO-first

Tento přístup jsem sám dlouhodobě razil a zkoušel. Má výhodu v tom, že všechno napíšu jednotně – objektově – a okolí se přizpůsobí. Funguje to, má to dvě ale, na která narazím později.

Jsou to:

  • zbytečný kód
  • neefektivní využití

Entities should work without db

Tato myšlenka vychází z představy, že aplikace si drží svůj stav ve svých datových strukturách, je nezávislá a technicky, pokud by běžela stále a nepotřebovala by dotazovat žádná další data, by se mohla od db úplně odpojit.

Ono je pěkné udělat si všechno v aplikaci, jenže tím vyvstává několik problémů.

Během té doby, než si stihnu udělat svoje, se mi můžou stavy jednotlivých klientů rozjet. Čím delší dobu s db nemluvím, tím pravděpodobněji a tím víc.

Navíc (což je věc spíš pro funkcionálního vývojáře) dělám aplikaci stavovou, i kdyby dovedla být bezstavová. To je v FP antipattern, ale to tu asi nebudu moc rozvíjet, Doctrine je OO-first.

The database is just saving things

Představte si, že najmete extrémně talentovaného vývojáře a pak ho necháte celé dny jen řadit soubory od největšího. Tak tohle je přístup, kdy z databáze uděláte jen hloupé úložiště.

Databáze není jen „saving things“. Vývojář si volí vyvážení toho, co chce mít v aplikaci a co v databázi. Chci mít triggery v aplikaci, nebo v databázi (já většinou v db)? Chci mít validaci v aplikaci nebo v databázi (většinou v aplikaci)? Reimplementace vlastností db v aplikaci sice obvykle zvedá horizontální škálovatelnost (kterou v ČR skoro nikdo nepotřebuje), ale za cenu výkonu, produktivity vývojáře a možností některých optimalizací.

Nevyužít dovedností databáze je nevyužitý potenciál. Snadno se vám může stát, že budete psát stovky řádek kódu tam, kde by se dalo zavolat jedno trochu chytřejší SQL. S pomocí WITH RECURSIVE můžu snadno procházet stromy a grafy přímo v databázi. Místo toho, abych na db pálil stovky dotazů, které práce s grafem často způsobí, pošlu jeden dotaz.

Design entities first

Nadesignovat nejdřív entity předpokládá, že vím, jak entity použiju.

Mně se ustálil úplně jiný přístup.

Do databáze dám data ve 3. normální formě. Do view dostávám data tak, jak je šablona chce a použije, žádná zbytečná data do šablony nejdou.

Opakují se mi datové struktury, které používám ve view.

Po čase, když se mi ty datové struktury ustálí, vytvořím z nich pojmenované entity. Do té doby jsou to jen amorfní pole. Entita je v mém aktuálním způsobu fungování definována použitím, ne daty samotnými.

Define mappings after designing the entities

Kdybych chtěl být tvrdý, řekl bych, že nemám žádné mappings. Není to úplná pravda. Mám mappings mezi tím, co je v databázi a tím, co potřebuje view nebo algoritmu, do kterého data jdou. Je to konverze, ke které by stejně došlo.

V žádném případě nemám nic jako mapování mezi databázovou strukturou a nějakými akademickými entitami, o jejichž použití ještě nemám nejmenší tušení.

ORM, která tohle dělají a vývojáře do toho nutí, tím plýtvají časem člověka i výkonem počítače. Když chci něco dát do AJAXu, tak ten response z pole serializuju rovnou do JSONu a žádný mezikrok s mapováním nemám.

Keep collections hidden in your entities

Jasně, je to logický krok, když chcete vytvořit čistě objektový svět, ve kterém není žádná databáze, nejsou primitivní typy, nic takového. Jen objekty, o jejichž vnitřní implementaci nic nevíme.

Mně se ukázalo, že tahle akademická čistota obvykle vede ke zbytečné mezivrstvě, která mi nepřijde ekonomicky racionální.

Minimalistické řešení se podívá na použití a vrátí data, která pro daný účel potřebuju. U toho je minimum konverzí, které neslouží mému účelu. To je můj stávající přístup. Pokud vytahuju data, která potřebuju a pokud potřebuju kolekci, vrátím si kolekci a pracuju s kolekcí.

Entities should always be valid

S tím souhlasím. Jen na to koukám asi jinak než autor. Mně z jeho přístupu přijde, že objekt sám má jen takové metody, které vždy končí validním stavem.

Protože já exponuju datové struktury ven, nechávám je volně měnit. V atomické změně musí být na konci i validační krok. Tzn. místo toho, abych měl metodu v objektu (čisté OOP), aplikuju existujícící funkce na datovou strukturu (FP přístup) a na konci měnící funkce zavolám validaci té entity a ona buď projde, nebo vyhodí výjimku.

Takový přístup mi snižuje duplikaci kódu, nemusím přenášet metody do objektu, místo toho znovu používám funkce, které jsem si napsal už dříve. Celkem to šetří čas, hlavně pokud se entity nějak podobají.

Favour immutable entities

Ano, samozřejmě. Jenže pokud vím, že nějaká data hodlám desetitisíckrát upravit, udělám je radši tranzientní (in-place editovatelné) a postarám se, aby mutace proběhla pouze v jednom threadu, ostatní thready můžou jen číst.

Čímž se dostáváme k další věci. Výhody immutable se ukážou v okamžiku, kdy píšete konkurenční paralelizovaný kód. To v PHP nijak moc nejde, možnosti jsou omezené a nikdo příčetný to nedělá. Takže ten ústřední benefit z toho není. V jiných jazycích, kde člověk provozuje více threadů, dává immutable daleko větší smysl. Takhle mi přijde, že autor skočil na populární trend a dává do PHP něco, co PHP nepotřebuje.

Avoid soft deletes

Příliš silné tvrzení.

Autor se dříve odkazoval na to, aby člověk zvážil event sourcing. Ano, když máte ES, nepotřebujete soft deletes. Jenže implementovat event sourcing je řádově těžší než soft deletes (každé je na něco jiného). Někdy jediné, co chcete, je definovat si k tabulce users view v_active_users, který se chytá na „deleted_at“ IS NULL. Vše ostatní je pak jednoduché. Své dotazy jen píšete proti danému view.

Soft deletes bych nepoužil tam, kde mi přibývá spousta řádek, které potom mizí. Moc často se mi nestává, že by to bylo čisté použití databáze. Většinou, když se tohle stane, někdo zneužívá databázi tam, kde má být nějaká fronta.

Eager loading is useless

Já si myslím, že to, co potřebujete, není lazy loading, ale async přístup k databázi. Lazy loading občas ušetří nějaká data. Prostě proto, že (jak jsem kritizoval už dříve) jsem si navymýšlel akademické entity obsahující data, která nepotřebuju. Tím, že je nenačítám, snižuju dopad toho, že jsem něco udělal neoptimálně už předtím.

V mém přístupu nemá lazy loading žádný význam. Z databáze mi lezou data, která potřebuju pro view, algoritmy a neobsahují nic navíc.

Pracuju s kurzorem do db, takže se načítají postupně, tímto lazy jsou, ale jinak, než je nejspíš myšleno v článku.

Mnohdy se ale vyplatí získat data asynchronně, zejméně pokud mám data číst z pomalého nebo nespolehlivého zdroje. Dám je načíst, zatím si udělám jiné věci a nakonec se podívám, jestli mi to už přišlo.

Závěr

Způsob, jak pracuju s databází teď, je hodně odlišný od mainstreamu, ale kód, který z toho leze, je krátký, udržovatelný, velmi výkonný.

Jaký je váš přístup k databázi?

Komentáře

Subscribe
Upozornit na
guest
6 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Martin Fris

Zaujimavy pohlad na vec z pohladu cloveka, ktory preferuje FP. Neviem ci je vsak korektne takto pisat o ORM, kedze neboli vyzdvihnute realne problemy ORM, iba alternativne moznosti bez pouzitia ORM. Nevadi :)

K vete o presune logiky z DB do kodu a o skalovani, ktore v Cechach nikto nepotrebuje – tuto nesuhlasim, presun logiky z DB do kodu uz na zaciatku je najlahsia cesta k tomu, aby sa neskor skalovalo, ak to bude potrebne. A ak ta potreba pride, tak databaza zostane uzkym hrdlom a cela aplikacia sa bude moct zahodit a napisat na novo, pripadne bude potrebne celkom velmi zrefaktorovat jadro. Technickemu dlhu nikto neutecie :) Aj na mensom Slovensku sme si mysleli, ze skalovat nebude treba a zrazu mame takmer 2TB velku databazu ;)

Petr Jasník

Díky za jiný pohled na věc, ale zdá se mi, že si trochu zapomněl, že se to týká pouze Doctrine ORM.

Doporučuju záznam celé přednášky, který je přínosnější než samotné slajdy
https://vimeo.com/channels/phpday/134178140

The database is just saving things

Doctrine ORM se snaží být nezávislá na typu DB (MySQL, Postgre, atd..). Doctrine ORM se snaží být „database agnostic“. Pokud využiješ naplno potenciál dané DB (triggery, views, procedury), přijdeš o možnost, bez úpravy kódu, přejít na jinou DB. Jednoduše proto, že daná DB např. triggery vůbec nepodporuje.
Jasně, v reálném světe se moc často nestává, že se přechází na jinou DB. Doctrine umožňuje jednoduché přepnutí DB, proto je databáze jako hloupé uložiště „best practice“. Možnost úpravou jednoho řádku přepnout aplikaci z MySQL na SQLite se někdy hodí.

Entities should work without db

Závislostí na DB je zde myšleno např. generování ID entity pomocí auto_increment indexu v DB, kdy entita nemá žádné ID dokud neproběhne uložení do databáze a nelze s ní tak dále pracovat. Generovat UUID v konstruktoru entity místo použití auto_increment je tedy vhodnější a není potřeba komunikace s DB.

Design entities first

Řekl bych, že toto spíš vychází z toho jak celá Doctrine funguje. Jednoduše si nadefinuješ entity a jedním příkazem vytvoříš celou databázi. Databáze je díky tomu pouhým implementačním detailem a je jedno jakou nakonec zvolíš (viz. předchozí bod).

Avoid soft deletes

V tomhle naprosto souhlasím, oba způsoby jsou vhodné na něco jiného a většinou je implementace Event sourcingu o tolik složitější, že se od toho prostě upustí.

Keep collections hidden in your entities

Zase to vychází z toho, jakým způsobem funguje celé mapování. Kolekce si klidně z entit vracet můžeš, ale pouze jako nový objekt nebo jako immutabilní. V dokumentaci se doporučuje toArrray() a je to jen kvůli tomu, že doctrine kolekce nejsou immutabilní a jakýkoliv vnější kód může kolekci jakkoliv měnit (může se stát nevalidní). Změny se pak při uložení entity, z které byla kolekce předána projeví i v DB (pokud není namapováno jinak). Entita tak nemá žádnou kontrolu nad stavem kolekce, kterou si předal ven.
Pokud potřebuješ upravovat kolekci vně entity, můžeš ji po úpravě vždy entitě nastavit setterem.

Všechno jsou to doporučení a né dogma. Každé doporučení má své „ale“ a vždy bude záviset na konkrétním případu. Tvůj způsob použití může být „krátký, udržovatelný, velmi výkonný“, ale cílem těchto best practices není „krátký, udržovatelný, velmi výkonný“ kód, ale co nejvíc bezproblémové a pro většinu případu nejjednodušší použití celé Doctrine. Best practices pro „krátký, udržovatelný, velmi výkonný“ kód by určitě vypadaly jinak a asi obsahovaly hodně z toho co píšeš.

Svaťa Šimara

Možnost úpravou jednoho řádku přepnout aplikaci z MySQL na SQLite se někdy hodí.

Zcela souhlasím, pro normální provoz používáme MySQL, systémové testy pouštíme kvůli rychlosti na SQLite a začínáme pošilhávat po PostgreSQL (kvůli práci s jsony). Díky Doctrine tento přechod nejen bude možný, ale bude i zdarma.

vojtechkurka

Pouzivat v produkci MySQL a na testy SQLite mi neprijde dlouhodobe jako dobrej napad, ty testy pak maji polovicni hodnotu. Lepsi je zlepsit nastaveni DB pro testy, nebo jejich spousteni paralelizovat. Ziskate tak i spolehlive testy pro upgrady DB na novejsi verze.
A jestli je pro vas nejvetsi motivace pro prechod na Postgres JSON, zkuste JSON implementaci v MySQL 5.7

Petr Jasník

začínáme pošilhávat po PostgreSQL (kvůli práci s jsony)

Z jaké verze MySQL přechod zvažujete? V MySQL 5.7 už nativní podpora pro JSON je.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.