NetteDatabase vs. Doctrine – klady zapory, zkusenosti
- besir
- Člen | 170
Ahoj,
aktualne pouzivam Nette s Dibi, ktere je v podstate skvele, pokud pracuji
s Nette jako takovým. Jenže v posledním projektu jsem na frontend poiužil
kendo UI a tam už torchu narážím na použitelnost… rozuměj, že každá
data musím přeskládat, nebo jim unsetnout položky pokud si pro kendo při
načtení pošlu nějaká pomocná data…
Proto pro další projekt chci použít něco… ale ještě nevím co… budou to takové klasické klubové stránky… textové stránky, fotogalerie, forum, členské profily etc. a myslím si, že pracovat s daty jako objekty by v tomto případě nemuselo být na škodu, ale zároveň nechci použít kanón na vrabce, protože hádám, že klub bude mít třeba do 100 členů…
Proto se ptám, doctrine ORM, nebo NDB?
Co má jaké výhody, nevýhody z pohledu programátora na používání?
Díky
- Oli
- Člen | 1215
Ahoj, teďka začínám v jednom projektu pracovat s Doctrine. Předtím jsem používál výhradně NDBT. Ještě jsem přemýšlel nad Nextras\ORM. Hlavní důvod pro Doctrine bylo pro mě, že Nextras nemá žádnou dokumentaci. Doctrine má dokumentaci a téměř cokoli je buď tam nebo na stackOverflow.
Zatím jsem se k ničemu velkýmu nedostal, ale práce s entitama mě přijde naprosto boží. A práce s n:m tabulkama je taky skvělá. Spolu s Kdby/Doctrine je to skvělej nástroj.
Někde jsem četl, že pokud máš aspoň jednou m:n, tak se ORM vyplatí a musím to potvrdit.
Uvidím na konci projektu, jestli budu mluvit stejně :-)
- Zax
- Člen | 370
Doctrine podle mě není kanón na vrabce ani v případě menších projektů. Je sice fakt, že je docela velká, a že se ji člověk musí naučit používat (a nakonfigurovat), což chvíli trvá, ale vrátí se to ;-) Doctrine ti dokáže částečně nebo i úplně zautomatizovat věci jako generování databáze (Doctrine umí oba směry – z entity vygenerovat databázi, z databáze vygenerovat entity) a migrace, existují i různá rozšíření, která ti pořeší běžné potřeby – překlady, stromy, soft-delete apod.
Osobně bych NDB použil jen na opravdu malé projekty, kde nehrozí nějaké rozšiřování. Jakmile je v projektu potřeba ukládat strom, tak už bych to řešil jedině přes Doctrine, protože udělat (správně) strom v NDB včetně manipulace není zrovna triviální záležitost.
- besir
- Člen | 170
Zax napsal(a):
Osobně bych NDB použil jen na opravdu malé projekty, kde nehrozí nějaké rozšiřování. Jakmile je v projektu potřeba ukládat strom, tak už bych to řešil jedině přes Doctrine, protože udělat (správně) strom v NDB včetně manipulace není zrovna triviální záležitost.
Když mluvíš o tom stromu, tak ten mam implementovaný pro Dibi a ano, nebyla to zrovna triviální záležitost :D
- Lkopo
- Člen | 65
S Doctrine mám dobré skúsenosti, už pri práci so Symfony2. Ten systém entít je naozaj dobrý, takisto to automaticky generuje getters & setters a pri doctrine:schema:update to aj generuje automaticky foreign keys aby bolo všetko prepojené tak, ako to má byť. Pokiaľ sa pamätám, menšou nevýhodou je práca s indexmi (keď chceš vyhľadávať), vraj to v základe neponúka.
- besir
- Člen | 170
@Lkopo No na tomto projektu, jelikoz bude jedine vyhledavani ve foru tak asi pouziju nejaky search engine, takze to mi jako nevyhoda asi nevadi…
@hrach asi jej vyzkouším na dalším projektu… tedy až bude dokumentace v nějaké rozumné fázi… nemám totiž uplně moc zbytečného času
Editoval besir (19. 9. 2014 21:52)
- David Ďurika
- Člen | 328
ja som (davno) pouzival NDB potom Docrtrine a nekoniec som skoncil pri LeanMapper od @Tharos https://github.com/…s/LeanMapper demo: https://github.com/…os/Microsite s dokumentaciu je na tom tiez horsie… ale tu je sice kvalitna dokumentacia no k starej verzii http://leanmapper.com/ no na zakladne pochopenie to staci…
- Filip Procházka
- Moderator | 4668
Doctrina je skvělý sluha ale zlý pán. Časová investice se ji naučit není malá, ale vyplatí se. Pak je tam taky problém ten, že špatným použitím se snadno můžeš takříkajíc snadno střelit do nohy. Ale to se taky časem naučíš a zase tolik toho není.
Doctrine je vlastně strašně jednoduchej nástroj, který ale nemůžeš používat bez toho, abys chápal koncepty na kterých je postavená. Pokud se tedy naučíš OOP (nebo se ho aspoň naučíš používáním Doctrine) a těch X vzorů které jsou kolem Doctrine a na kterých stojí, tak ten zbytek už je brnkačka.
Já bych už ale web na ničem jiném nedělal, pokud by měl být na Nette nebo Symfony :)
Azathoth napsal(a):
Já jsem s doctrine 2 začal před několika týdny. Zezačátku to byl boj, peklo, porod (doplňte další slova), ale jakmile jsem se naučil, jak pracovat s dědičností a asociacemi, tak to jde jako po másle a je to strašně pohodlné.
Prakticky pořád jsem na nette chatu, klidně se dojdi zeptat kdyby jsi něco měl problém najít nebo pochopit :)
@Lkopo No na tomto projektu, jelikoz bude jedine vyhledavani ve foru tak asi pouziju nejaky search engine, takze to mi jako nevyhoda asi nevadi…
Search nad MySQL stejně stojí za prd. Zrovna na rohlik.cz ladíme ElasticSearch, takže můžeš v Kdyby brzo očekávat odladěný knihovny který nejenom že řeší ES, ale taky i propojení s Doctrine ;)
- Pavel Macháň
- Člen | 282
David Ďurika napsal(a):
ja som (davno) pouzival NDB potom Doctrine a nekoniec som skoncil pri LeanMapper od @Tharos https://github.com/…s/LeanMapper demo: https://github.com/…os/Microsite s dokumentaciu je na tom tiez horsie… ale tu je sice kvalitna dokumentacia no k starej verzii http://leanmapper.com/ no na zakladne pochopenie to staci…
@besir Zatím také používám LeanMapper od @Tharos a vřele doporučuji. Časem se chystám také na Doctrine, ale momentálně není čas se sní učit.
- Azathoth
- Člen | 495
Filip Procházka napsal(a):
Doctrine je vlastně strašně jednoduchej nástroj, který ale nemůžeš používat bez toho, abys chápal koncepty na kterých je postavená. Pokud se tedy naučíš OOP (nebo se ho aspoň naučíš používáním Doctrine) a těch X vzorů které jsou kolem Doctrine a na kterých stojí, tak ten zbytek už je brnkačka.
Jaké vzory máš na mysli? Já žádné nepoužívám a dost možná je to chyba. Máš na mysli návrhové vzory? A jaké jsou kolem Doctrine?
- Tharos
- Člen | 1030
Ahoj,
díky své současné práci jsem denně ve styku s Doktrínou a chtě nechtě jsem do jejího světa musel proniknout. Byla to pro mě velmi zajímavá zkušenost. Zjistil jsem, že jsem vůči ní měl řadu předsudků, že to je náhodou kvalitní nástroj a také jsem si uvědomil, že si dokážu představit, že tak… jeden projekt z deseti bych namísto Lean Mapperu založil nad Doctrine. A to v případě, že bych k sobě měl kolegy ovládající právě Doktrínu a taky (teď možná někoho překvapím) kdyby ten projekt nebyl příliš rozsáhlý.
Na Doktríně se mi líbí spousta věcí, ale celkový dojem je, že je to prostě port Hybernate ORM z Javy, který na platformu PHP tak dobře nepasuje. PHP není Java (a to není žádná kritika jednoho nebo druhého, obojí má své místo pod sluncem).
Důležitou otázkou je, co od ORM očekáváš. Pokud to, aby data, která čteš z databáze, byla zapouzdřena v nějakých konkrétních třídách, mohl jsi používat typehint, snadno traverzovat mezi souvisejícími daty, Doktrína Ti vyhoví dobře. Ale stejně dobře Ti při takových minimálních požadavcích vyhoví i Lean Mapper nebo jiné podobné knihovny. Pokud ale očekáváš víc, jako například možnost mít bohaté doménové objekty ve stylu DDD, buď připraven na to, že s Doktrínou velmi snadno narazíš.
Aby to ale nebyla slova do větru, uvedu hned jeden příklad. Doktrína, jak je známo, není úplně nejrychlejší. Je to tím, že hydratace entit pomocí reflexe je (oproti Javě) opravdu strašně pomalá. Nastavit nějakou položku pomocí reflexe je cca o řád pomalejší, než pomocí setteru. No a když se pak dostaneš do úzkých (což se dostaneš u většího projektu velmi brzy), zkušení Ti poradí hydratovat data do polí namísto do objektů. Jenomže máš-li v duchu DDD spoustu logiky právě v těch objektech, máš v podstatě neřešitelný problém.
Někdo nedávno napsal, že Lean Mapper je vlastně jenom takové skoro-ORM. Já jsem nad tím přemýšlel a začalo se mi to označení líbit, já ho asi začnu propagovat jako sORM (simplified ORM). :) Došlo mi, že to je jeden z důvodů, proč mi vyhovuje víc, než Doktrína. Pořád máš relativně blízko k SQL (což mi přijde skvělé), je čistě na Tobě, jak moc důkladnou abstrakci si zavedeš, v podstatě nenarazíš na výkonnostní problémy a mohl bych pokračovat.
I když už Doktrínu ovládám řekněme středně dobře a dost se můj postoj k ní zlepšil, pořád mi tak nějak úplně nesedí k PHP. Tuplem pak ne k Nette.
Vím, že mám obrovský dluh co se dokumentace týče. Některé věci už jsem fakt mohl mít zdokumentované dávno (API). Některé věci jsem ale chtěl nechat trochu vyzrát, a to hlavně know-how, jak nad Lean Mapperem stavět aplikace. Ona totiž existuje hned řada způsobů a sám jsem si jich hned několik vyzkoušel. Jeden takový už mám ale pokřtěný ohněm (na jednom e-shopu) a maximálně se mi osvědčil. A proto bych rád, jakmile ten e-shop dokončím :), o tom napsal alespoň pár článků. Doufám, že tím trochu smažu svůj dokumentační dluh, protože předání komplexního know-how považuji za ještě zajímavější, než pouhou API dokumentaci.
Takže já si myslím, že s takovým Lean Mapperem bys neprohloupil. :)
Editoval Tharos (20. 9. 2014 19:38)
- Zax
- Člen | 370
Tharos napsal(a):
Aby to ale nebyla slova do větru, uvedu hned jeden příklad. Doktrína, jak je známo, není úplně nejrychlejší. Je to tím, že hydratace entit pomocí reflexe je (oproti Javě) opravdu strašně pomalá. Nastavit nějakou položku pomocí reflexe je cca o řád pomalejší, než pomocí setteru. No a když se pak dostaneš do úzkých (což se dostaneš u většího projektu velmi brzy), zkušení Ti poradí hydratovat data do polí namísto do objektů. Jenomže máš-li v duchu DDD spoustu logiky právě v těch objektech, máš v podstatě neřešitelný problém.
Hmm to by mě zajímalo, nakolik je toto pravda.. odkazovaný článek naopak píše, že Doctrine není pomalá, pokud si ji správně nakonfiguruješ (query cache, metadata cache, automatické generování proxy tříd, třeba i result cache pro selecty). Navíc třeba ta result cache, pokud se nepletu, hydratuje jen po invalidaci a jinak tahá rovnou hydratovaná data z cache (přiznám se, že úplně nevím, jak moc košér je zapínat tuto cache na prakticky všechny selecty kromě dat, která se často mění, ale zatím jsem nenarazil na problém).
- Filip Procházka
- Moderator | 4668
V Doctrine 2.5 přibyla „Second Level Cache“ která bude úplně jiný kafe a s rychlostí aplikací brutálně zahýbe :)
- Tharos
- Člen | 1030
Zax napsal(a):
Hmm to by mě zajímalo, nakolik je toto pravda.. odkazovaný článek naopak píše, že Doctrine není pomalá, pokud si ji správně nakonfiguruješ (query cache, metadata cache, automatické generování proxy tříd, třeba i result cache pro selecty).
Já jsem na tenhle článek odkázal schválně, protože přesvědčování, že Doctrine není pomalá, v něm vypadá tak, že Ondřej popisuje, jaké všechny cache si kde pozapínat, aby nebyla pomalá. :) Takže ano, Doktrína je pomalá, ale existují na to náplasti. Některé vhodné více (cache), některé pro mě osobně méně (array hydratace).
Abych ještě dokreslil ty problémy s array hydratací… Není to dokonce
jen o logice, která se v entitách může nacházet, ale i o tom, že
entita nahydratovaná do pole neprojde žádným typehintem, který očekává
entitu, takže na řadě míst aplikace může vznikat polymorfismus,
který řeší, jestli dostal entitu Author
nebo pole, které
obsahuje informace o autorovi atp.
Rada, aby se na produkci vypnulo generování proxy tříd při každém requestu, mě až pobavila. Kdo si nechává na produkci při každém requestu generovat proxy třídy? Pro nezasvěcené je to podobné, jako kdybyste si na produkčním serveru při každém requestu nechali přegenerovat Nette DI kontejner.
Navíc třeba ta result cache, pokud se nepletu, hydratuje jen po invalidaci a jinak tahá rovnou hydratovaná data z cache (přiznám se, že úplně nevím, jak moc košér je zapínat tuto cache na prakticky všechny selecty kromě dat, která se často mění, ale zatím jsem nenarazil na problém).
A co když mám takovou entitu, kterou není možné z nějakého důvodu serializovat? Jak vůbec ta cache funguje, tzn. v jaké podobě a co přesně ukládá? Ptám se, jestli mi něco neuniká – rád svůj postoj případně přehodnotím!
- Zax
- Člen | 370
@Tharos
No jak říkám, Doctrinu ještě moc prozkoumanou nemám (začal jsem s ní před dvěma měsíci), a v podstatě máš pravdu, ale to můžem rovnou argumentovat tím, že jakákoliv aplikace (až už s db nebo bez) bude bez použití cache pomalá ;-) . Prostě když se to udělá správně, tak by to pomalé být nemělo (přinejmenším při zobrazování dat, s cache jsou změny pochopitelně pomalejší). A ano, určitě souhlasím, že array hydratace jde tak nějak proti všem důvodům, proč používat ORM, proto jsem taky zmínil tu result cache, což je celkem dobrý kompromis.
Entity v Doctrině jsou serializovatelné, protože jde jen o hloupé obálky, člověk se musí snažit a cpát tam nějaký resource aby entita nešla serializovat. Tohle mě upřímně dost vadilo u YetORM (nevím jak je na tom LeanMapper či Nextras\ORM) kde entity v konstruktoru chtějí ActiveRow, který chce Selection, který chce Connection a Connection chce PDO, který nejde serializovat. Člověk pak hledá berličky v podobě toArray() a fromArray() a nebo prostě cachuje rovnou výstup v šabloně, což řeší jenom část problému. V Doctrině zavolám $query->useResultCache(TRUE, NULL, ‚tag‘) a prostě to funguje, vrací se entity (naplněné, pod správou UnitOfWork, možná proxy entity kvůli lazy-loadingu, nevím přesně, ale prostě se s nimi dá plnohodnotně pracovat ;-) ), do databáze se pošle dotaz jen napoprvé a je možné si ty result cache tagovat a invalidovat.
Editoval Zax (20. 9. 2014 22:49)
- Filip Procházka
- Moderator | 4668
@hrach když tvá aplikace potřebuje L2 cache tak bych se nebál tvrdit, že už nemáš úplně malou aplikaci a tedy stojí za zvážení VPS, případně ne-sračkoidní hosting.
Entity v Doctrině jsou serializovatelné, protože jde jen o hloupé obálky, člověk se musí snažit a cpát tam nějaký resource aby entita nešla serializovat.
@Zax To není tak úplně pravda, pokud máš v relacích nějakou proxy nebo kolekci, tak ty mají reference na Connection a to už tak snadno nezeserializuješ.
Ovšem to se dá strašně snadno vyřešit napsáním metody
__sleep()
nebo použitím jiného mechanismu. Navíc BaseEntity
v Kdyby za tebe polovinu problému (to co bys měl všude stejně)
už řeší.
- Šaman
- Člen | 2666
Na LeanMapperu mi (i když výjimečně) chybí jediná věc – je to ORM,
takže všechny entity jsou navázány na databázi. Z principu by to tak mělo
dělat každé ORM, ale viděl jsem jedno (ORM od Petra Procházky), kde entity
nevyžadovaly databázi, až mapper je do ní uložil. Kromě
serializovatelnosti (tedy i cachovatelnosti celého objektu i s vnitřními
stavy apod.) to mělo tu výhodu, že bylo možné vytvořit entity, které
v databázi vůbec nejsou a pracovat s nimi jako s každou jinou entitou.
LeanMapper je lean a chápu, proč tohle neumí. S potřebou entit bez
databáze jsem se setkal na jednom projektu asi z padesáti a pro ten projekt
by bylo vhodnější napsat si zcela vlastní model, nebo použít jiný
nástroj. Druhý důvod, proč něco takového chtít, by bylo asi použití
nerelační databáze.
NDbT a NotORM a všechna ORM postavená nad nimi jsou na tom samozřejmě stejně – oba nástroje jsou jsou dotazovače do relační databáze.
A docela by mě zajímalo, jak je na tom Doctrine? Je možné tam vytvořit entitu, která neexistuje v databázi a připojit k ní nějaké vazby?
- David Matějka
- Moderator | 6445
@Šaman jak pise filip, doctrine entity nevyzaduji spojeni
k databazi, krome nejakych proxy trid, lazy kolekci atd.
entity v doctrine nemuseji od niceho dedit a mohou se pouzivat jako stand-alone
objekty bez entity manageru. mapovani na databazi nemusi byt v anotacich, ale
treba v yaml nebo v xml, takze trida nemusi vedet, ze je doctrine entita
- Šaman
- Člen | 2666
Díky, tak pak je jasné, že znám jeden projekt, kde by se Doctrine hodila více. Ale jak píšu, je to jeden projekt z několika desítek, které jsem potkal.
Jinak za entitu považuji entitu z ER diagramu, nikoliv sloupec, nebo tabulku z návrhu databáze. Entitu si třeba v některých fyzikálních modelech mohu vytvořit, navázat na nějaké další, nechat projet výpočet a pak si třeba jen načíst výsledek celého takového modelu. (Tedy entita v tomto smyslu vůbec není přepravka na data, ale plnohodnotný člen objektového modelu, která si ručí za své výpočty. A navázáním na jiné (už klasické) entity zajistím třeba korektní vazbu na výchozí hodnoty v databázi. Takový školní příklad by byl systém těles a pružin – tělesa jsu uložená v databázi (počáteční stav) a pružina je jen entita, která je schopná vazby s tělesem a jinak obsahuje kupu výpočtů. A mě zajímá stav pružiny v nějakém čase, ale vůbec ji nepotřebuji ukládat, protože takových virtuálních pružin si během jednoho výpočtu vytvořím několik set a pak třeba vybírám tu nejvýhodnější.)
- Tharos
- Člen | 1030
Zax napsal(a):
Entity v Doctrině jsou serializovatelné, protože jde jen o hloupé obálky
Mít entity jako hloupé obálky mně osobně přijde hloupé. Vždyť tak se připravuješ o sílu OOP (mít zapouzdřená na jednom místě data a logiku, kterou s nimi pracuje). Kam dáš logiku, která s hodnotami v entitě pracuje? Do samostatné třídy?
Tohle mě upřímně dost vadilo u YetORM (nevím jak je na tom LeanMapper či Nextras\ORM) kde entity v konstruktoru chtějí ActiveRow, který chce Selection, který chce Connection a Connection chce PDO, který nejde serializovat. Člověk pak hledá berličky v podobě toArray() a fromArray() a nebo prostě cachuje rovnou výstup v šabloně, což řeší jenom část problému.
V Lean Mapperu entity z Tebou uvedeného důvodu přímočaře serializovat
nejdou. Entita zapouzdřuje Row
, v jehož útrobách se nachází
až DibiConnection
. Je to v podstatě ekvivalent toho, když máš
v Doktríně v entitě proxy třídy (v jejichž útrobách by ses AFAIK taky
dořezal přes EntityManager
až k PDO).
Co chci k tomu ale hlavně říct… Ono by ty entity přes nějaké
toArray
nebo sleep
určitě serializovat šlo, ale já
jsem to nikdy nepotřeboval. Skoro mám dojem, že serializace se stejně
vždycky řeší jenom kvůli cache, a tím, že nemám s výkonem sebemenší
problém, tak jsem prostě ani na jednom z několika (teď už desítek!)
projektů, které mi pohání Lean Mapper, nemusel serializaci řešit.
Na co jiného se ještě serializace hodí?
Editoval Tharos (21. 9. 2014 20:03)
- Šaman
- Člen | 2666
Právě na to ukládání objektů kamkoliv jinam, než do relační db. Ale
to už opouštíme běžný model práce. Nerelační databáze, ukládání na
disk (třeba objekt s nastavením uživatele mít v session a nemuset ho vždy
vytvářet znovu), nebo dokonce předávání objektů mezi dvěma
samostatnými částmi aplikace po síti, pokud každá běží na jiném
stroji.
Pardon, zavzpomínal jsem si na pár teoretických přednášek ze studií. Na podobné věci by použil PHP jenom masochista. :D
- Zax
- Člen | 370
@FilipProcházka Díky za vysvětlení. Tak nějak jsem tušil, že (minimálně v Kdyby) to nebude úplně hloupá obálka a bral jsem to spíš jen ze svého „uživatelského“ pocitu – prostě napíšu entitu s pár vlastnostmi, Doctrina mi vlastnosti naplní daty z databáze a jede se :-) Je fakt, že vazby na kolekce ještě moc prozkoumané nemám. Nicméně velké díky za Kdyby/Doctrine, podvědomě tuším, že díky této knihovně mám mnohem méně práce, než kdybych pracoval s „čistou“ Doctrinou ;-)
@Tharos No dobře, tak polo-hloupé obálky třeba s nějakou validací a třeba i chytřejšími gettery (které akorát přeformátují část svých hodnot do nějaké často potřebné podoby) a settery, ale ano, vyloženě logiku pro práci s hodnotami bych dal někam jinam, protože… proč ne? O_o
Pro mě entita = objekt obsahující data z databáze a nečekám od toho nic víc, než že mi předá data z databáze. Co se má s těmi daty dělat už entitu zajímat nemusí. Stejně tak jako entita neví nic o tom, kam jsou její data ukládána, nevidím důvod, proč by měla vědět o té „druhé straně“ – tedy o tom, co s těmi daty potřebuje dělat aplikace. Čistě můj názor.
Ano, serializaci řeším jenom kvůli cachování. Entity a vůbec obecně výsledky dotazů mi přijdou jako ideální kandidát na cachování. Stromové menu, které se mění jen jednou za čas, prostě nechci tahat z databáze při každém zobrazení stránky a ani ho nechci cachovat v šabloně, protože nad položkami dělám další kontroly (a ty kontroly očekávají entitu).
V tomhle směru mi Doctrina neháže klacky pod nohy (děkuji @FilipProcházka za odstranění klacků ;-)), protože minimálně entity, které nejsou proxy, by měly jít cachovat naprosto bez problémů už z jejich podstaty (nevyžadují PDO). Naopak u YetORM/LeanMapperu apod. už z podstaty entit vyplývá to, že je nejde (pohodlně) cachovat. Mně to přijde jako obrovská škoda. Buď můžu díky síle NDB/Dibi položit efektivní dotaz na databázi, ale výsledek nenacachuji, nebo použiju Doctrinu, za hodinu možná vypotím jakž takž efektivní dotaz, ale potom můžu výsledek velmi jednoduše cachovat. Proč nemít obojí? :-D
- Tharos
- Člen | 1030
@Zax: OK, dostal jsi mě. Máš pravdu, že možnost serializovat entitu není k zahození. A proto ode dnešního dne Lean Mapper podporuje serializaci entit naprosto pohodlně a bez omezení. :)
Chtěl bych vyzdvihnout jednu hezkou vlastnost, kterou aktuálně ta serializace má. Pokud si vytáhnu z databáze nějakou entitu, postupně se pak z ní dotraverzuji k dalším entitám a až pak teprve uložím do cache tu původně načtenou entitu, po načtení dat z cache mohu provést úplně stejné traverzování, aniž by se cokoliv znovu načítalo z databáze. To by mohlo perfektně fungovat třeba při kešování toho menu, kdy pravděpodobně budeš postupně vypisovat nějakou hierarchickou strukturu.
No, napsal bych Ti na závěr enjoy, ale Ty Lean Mapper stejně nepoužíváš… :) A tak alespoň díky Ti za inspiraci a ať to slouží těm, kdo jej používají a doteď jim serializace chyběla (jsou-li tací).
Komu by se nelíbila implementace a práce se statickým prostorem, ať se pro zajímavost podívá, co vše si do statického prostoru ukládá Doktrína. :-P
Editoval Tharos (22. 9. 2014 8:47)
- Filip Procházka
- Moderator | 4668
@hrach napsal(a):
@FilipProcházka ale on tu nikdo nemluvil o velkych projektech, naopak se tu mluvilo o malych projektech, na ktery je Doctrine moc velka a pomala.
S tím právě nesouhlasím a myslím si že jde hlavně o efektivitu vývoje (která je v mém případě NDTB vs Doctrine nesrovnatelná). U malých projektů je maličko pomalejší web ten nejmenší problém.
@Zax napsal(a):
Pro mě entita = objekt obsahující data z databáze a nečekám od toho nic víc, než že mi předá data z databáze. Co se má s těmi daty dělat už entitu zajímat nemusí.
Tomuhle se říká Anémický doménový model a já i pan Fowler si myslíme, že to není úplně nejlepší :) Na malých projektech kde prostě tu logiku nemáš se nic neděje, ale na velkých projektech alespoň nějakou logiku v entitách potřebuješ, jinak je ti ORM nahouby a můžeš jít dělat třeba v AR, nebo čisté NDBT :)
Stejně tak jako entita neví nic o tom, kam jsou její data ukládána, nevidím důvod, proč by měla vědět o té „druhé straně“ – tedy o tom, co s těmi daty potřebuje dělat aplikace. Čistě můj názor.
To že se Entita neví jak je ukládána je přednost, jenže Entita je součástí doménového modelu a tedy bych jí úplně nebránil tušit na co její data budou použita. Například taková entita uživatele vůbec nikomu nemusí říkat ani hash hesla. Když chceš ověřit přihlášení, zeptáš se entity
public function verifyPassword($password)
{
return Passwords::verify($password, $this->password);
}
public function changePassword($password = NULL, $oldPassword = NULL)
{
if ($oldPassword !== NULL && !$this->verifyPassword($oldPassword)) {
throw new Nette\Security\AuthenticationException("The old password doesn't match");
}
$this->password = Passwords::hash($password);
return $this;
}
Neříkám že tohle je ten nejlepší způsob jak to vyřešit, ale je to naprosto validní způsob jak to vyřešit. Hash se stal implementačním detailem entity uživatele a už není anémická :)
protože minimálně entity, které nejsou proxy, by měly jít cachovat naprosto bez problémů
Technicky vzato, i ty proxyny jdou cachovat bez problémů, jenom si musíš
implementovat vlastní __sleep
, což je primitivní :)
@Tharos napsal(a):
Pokud ale očekáváš víc, jako například možnost mít bohaté doménové objekty ve stylu DDD, buď připraven na to, že s Doktrínou velmi snadno narazíš.
Tohle bych prosil rozvést, protože nesouhlasím :)
Komu by se nelíbila implementace a práce se statickým prostorem, ať se pro zajímavost podívá, co vše si do statického prostoru ukládá Doktrína. :-P
Jenom typy db fieldů, které jsou bezkontextové, takže je to jedno :)
- Zax
- Člen | 370
@FilipProcházka Jako já bych s tebou hrozně rád souhlasil, ale nepřijde mi, že je to až tak jednoduché, jak to zní ;-)
Co když ale nějaká logika v entitě vyžaduje nějakou službu? Mám si ji ručně předávat při každém volání té logiky? Mám nějak znásilnit Doctrinu, aby mi tu službu nějak automaticky injectovala? Nebo mám prostě copy&pastnout kód z té služby do entity?
Třeba na to je nějaké elegantní řešení, o kterém nevím, ale když ho neznám, tak radši nechám entity hloupé, aby mi nevznikl technický dluh, když najednou zjistím, že logika v entitě nabobtnala, vyžaduje služby a jánevímco a najednou musím přepsat půlku aplikace, kde se s entitou pracuje.
Každopádně se rád poučím, děkuju ti za cenné informace!
- bazo
- Člen | 620
Zax napsal(a):
Co když ale nějaká logika v entitě vyžaduje nějakou službu? Mám si ji ručně předávat při každém volání té logiky? Mám nějak znásilnit Doctrinu, aby mi tu službu nějak automaticky injectovala? Nebo mám prostě copy&pastnout kód z té služby do entity?
tak tam mas nieco zle
- Tharos
- Člen | 1030
Zax napsal(a):
Co když ale nějaká logika v entitě vyžaduje nějakou službu? Mám si ji ručně předávat při každém volání té logiky?
Ne, máš si ji do té entity normálně injektnout. :) Tak je to správně z pohledu OOP a také DDD. Viz například tenhle článek, na který strašně rád odkazuji, protože skvěle popisuje podstatu věci a názorně ukazuje, k jakým problémum vede pseudo-OOP.
To, jestli Ti konkrétní ORM dovolí snadnou injektáž závislostí do entit, už je ale další věc. Praxe je pak často právě o kompromisech kvůli použitým nástrojům. :)
@Tharos napsal(a):
Pokud ale očekáváš víc, jako například možnost mít bohaté doménové objekty ve stylu DDD, buď připraven na to, že s Doktrínou velmi snadno narazíš.
Tohle bych prosil rozvést, protože nesouhlasím :)
No, máme tu na stole už minimálně dvě komplikace. :) Lze narazit na nutnost hydratovat data do polí kvůli výkonu, čímž jde DDD hned do kytek (pokud neprogramujeme návštěvní knihu), a pak taky problematiku injektování závislostí do entit (což je v DDD naprosto legitimní záležitost). To se totiž v Doktríně děje přes všemožné hacky (listenery atp., však dobře víš) a jsou s tím spojené různé problémy (a perly, jako je zapersistování entity proto, aby do ní listener nalil závislosti atp.).
Já si prostě myslím, že ORM, které by šlo s čistým svědomím doporučit jako DDD-ready, by těmito neduhy trpět nemělo.
Editoval Tharos (24. 9. 2014 0:55)
- mkoubik
- Člen | 728
Zax napsal(a):
Co když ale nějaká logika v entitě vyžaduje nějakou službu? Mám si ji ručně předávat při každém volání té logiky? Mám nějak znásilnit Doctrinu, aby mi tu službu nějak automaticky injectovala? Nebo mám prostě copy&pastnout kód z té služby do entity?
Koukám že zmatení a buzzwordizace kolem DDD dosáhly lokálního maxima. Pokud máme na mysli entitu z DDD tak jejím úkolem (resp. úkolem agregátu) je udržovat vnitřní konzistenci (definovat sadu invariantů které zachovává). Např. „nezaregistrovanému zákazníkovi nelze změnit heslo“ nebo „odeslanou fakturu nelze upravovat“.
Pro ilustraci uvedu typický případ porušení tohoto principu – dejme tomu že vytvářím nějakou objednávku obsahující jednu položku:
$order = new Order();
$order->addItem($item);
Takhle to určitě dělá spousta lidí a přitom hned na prvním řádku je
problém – objednávka s nula položkami nedává smysl, správně by mělo
být něco jako $order = new Order($customer, $items);
.
Teď když si rozumíme tak k tomu „injektování služeb do entit“. Záleží co se myslí tou službou, pokud „service“ z DDD, tak viz níže, já mám ale podezření že @Tharos myslí klasický případ typu „při potvrzení objednávky potřebujeme poslat email“. To samozřejme nemá v entitě co dělat, protože to jestli se email odeslal nebo ne nijak nesouvisí s konzistencí té entity. Dá se na to dobře použít event sourcing, případně staré dobré „services“ z DDD.
Služby v DDD se totiž neinjektují do agregátů (tím méně do entit), ale naopak do služeb se injektuje repozitář, ze kterého se potřebný agregát vytáhne.
// ConfirmOrderService:
public function confirmOrder($orderId)
{
$order = $this->repository->getById($orderId); // throws AggregateNotFoundException
$order->confirm();
$this->confirmationMailer->sendEmail($order); // tady použijte kdyby/events :-)
}
Přičemž $this->confirmationMailer
není
„service“ podle terminologie DDD.
Jako jedna z výjimek kdy je potřeba nějaký objekt dostat do entity je strategy pattern, ale tam bych daný objekt předávál jen do konkrétní metody která ho potřebuje. Např. při potvrzování té objednávky bychom molhli potřebovat vygenerovat číslo objednávky:
public function confirm(IOrderNumberStrategy $strategy)
{
$this->confirmed = true;
$this->number = new OrderNumber($strategy->generateNumber());
}
BTW: rád bych si na posobotě pokecal s někým kdo si myslí že dělá DDD, přečetl aspoň „modrou knížku“ od Evanse a nějaký čas nad tím přemýšlel. Já mám zatím pocit že to všichni okolo mě dělají blbě (což může dost dobře znamenat že to dělám blbě já).
K tomu mám dva poměrně silné názory:
- pokud pracujete s entitou v presenteru (nedej bože v šabloně) tak to
děláte blbě:
Mimo modelovou vrstvu se pracuje s identifikátory a view/DTO objekty. Pomocí nich se komunikuje se „services“ a „repositories“.
- pokud používáte
Kdyby\Doctrine\Entities\BaseEntity
tak to děláte blbě:Settery jsou zlo a pokud vám je entita magicky generuje tak vás bude svádět je používat.
PS: DDD mnohem víc řeší jak máte komunikovat se zadavatelem, než jak máte programovat. Třeba takový „ubiquitous language“ považuju za mnohem důležitější princip než cokoliv co vyčtete z kódu. Takže to berte s rezervou.
Editoval mkoubik (24. 9. 2014 11:40)
- mkoubik
- Člen | 728
A teď konečně k té doctrine a DDD.
Mně při aplikování DDD vadí na doctrine úplně jiné věci a dost mě překvapilo že je nikdo nezmínil:
- Doctrine vynucuje že co entita to tabulka (nemluvím teď o dědičnosti).
Občas by se mi hodilo nějakou entitu z 1:1 vazby mapovat do stejné tabulky
(s potřebou mapovat jednu entitu do více tabulek jsem se zatím nesetkal.
Tohle částečně řeší value objecty v doctrine 2.5, ale stejně by se mi občas hodila možnost implementovat si úplně obecný mapper.
- Chybí mi možnost mapovat různé entity do stejné tabulky (opět
nemluvím o dědičnosti). Například s tou objednávkou bych chtěl pracovat
jinak v administraci a jinak na frontendu (viz „bounded context“ v DDD).
Zkrátka bych potřeboval dvě různé entity s různými metodami, které by
ale měly stejné properties, jejich vnitřní stav by byl vzájemně
provázaný a mapovaly by se do stejné tabulky. Takhle musím mít jednu
obecnou mega-entitu, která obsahuje metody ze všech bounded contextů.
Tohle by asi šlo nějak obejít pomocí dědičnosti a single-table-inheritance, ale nezkoušel jsem to a považuju to za prasárnu.
- V DDD neexistují many-to-one a many-to-many vazby, protože pokud nějaký
agregát obsahuje nějakou entitu, už ji nesmí obsahovat jiný agregát (ten
původní by nemohl zaručit svoji konzistenci když se mu někdo další hrabe
v entitách). Zároveň aggregate root potřebuje mít referenci (klidně
zprostředkovaně) na všechny své entity. Z toho vyplývá potřeba
používat unidirectional one-to-many vazby, které jsou v doctrine
problematické a vyžadují join table.
Tohle je myslím taky nějak vyřešeno v doctrine 2.5.
- Kvůli absenci many-to-* vazeb a pro izolaci zodpovědností ze na jiné
agregáty neodkazuje pomocí reference, ale pomocí identifikátoru. Nechci tedy
mít v objednávce v
$this->customer
entitu, ale chci mít v$this->customerId
identifikátor. Přesto bych ten identifikátor rád v db mapoval na cizí klíč.Tohle jde obejít, ale přímo v dokumentaci k doctrine píšou že se namají cizí klíče ukládat do properties, takže s tím asi můžou být problémy a považuju to za varování že doctrine není na DDD dělaná.
Editoval mkoubik (24. 9. 2014 11:22)
- Filip Procházka
- Moderator | 4668
@mkoubik doufám že se konečně potkáme na této posobotě a nezapomeneme (zase) si o tomhle pokecat :)
- Tharos
- Člen | 1030
@mkoubik: Díky za výborné příspěvky!
Nedalo mi to a trochu jsem se dovzdělal. Zjistil jsem, že mezi programátory existuje velké zmatení pojmů (a dojmů) a že (Rich) Domain Model (tak, jak je popisuje například Fowler) !== DDD (tak, jak jej popisuješ Ty a Evans).
Celá část Domain Model z PoEAA je shodou okolností dostupná online, takže tu už nemusím nic obšírně popisovat. Chtěl bych ale opravit svá zdejší tvrzení, protože kde jsem psal DDD, myslel jsem Domain Model.
V něm je injektování služeb do entit IMHO legitimní. Hezká ukázka je
už v odkázaném článku. Podívej se třeba na třídu
Product
:
class Product...
private String name;
private RecognitionStrategy recognitionStrategy;
public Product(String name, RecognitionStrategy recognitionStrategy) {
this.name = name;
this.recognitionStrategy = recognitionStrategy;
}
public static Product newWordProcessor(String name) {
return new Product(name, new CompleteRecognitionStrategy());
}
public static Product newSpreadsheet(String name) {
return new Product(name, new ThreeWayRecognitionStrategy(60, 90));
}
public static Product newDatabase(String name) {
return new Product(name, new ThreeWayRecognitionStrategy(30, 60));
}
Pokud bych vytvářel produkt přímo (konstruktor je public
),
určitě bych tam RecognitionStrategy
injektnul. Co je ale
důležité je, že Product
má asociaci k
RecognitionStrategy
, ví o ní a vyžaduje ji ke své práci.
Mohli bychom celou tu logiku vyhodit z entity do nějaké servisní vrstvy, ale to je právě přechod od Rich Domain Modelu k anémickému modelu, který se z velmi rozumných důvodů nedoporučuje.
Překvapuje mě, jak se Domain Model a DDD zjevně liší, ale nejsem odborník ani na jedno. Jen ve svých aplikacích čím dál více sleduji, že tíhnu k něčemu, co Fowler popisuje jako Rich Domain Model, a velmi se mi to osvědčuje.
To injektování závislostí v Lean Mapperu je shodou okolností právě
úplně triviální, stačí mít takovouto EntityFactory
:
class EntityFactory extends DefaultEntityFactory
{
/** @var Container */
private $container;
/**
* @param Container $container
*/
public function __construct(Container $container)
{
$this->container = $container;
}
/*
* @inheritdoc
*/
public function createEntity($entityClass, $arg = null)
{
return $this->container->createInstance($entityClass, ['arg' => $arg]);
}
}
Pak lze mít například entitu:
/**
* @property int $id
* @property float $price // see getPrice
* ...
*/
class Product extends Entity
{
/** @var CurrencyConverter */
private $currencyConverter;
/**
* @param CurrencyConverter $currencyConverter
* @param mixed $arg
*/
public function __construct(CurrencyConverter $currencyConverter, $arg = null)
{
$this->currencyConverter = $currencyConverter;
parent::__construct($arg);
}
/**
* @param string|null $currency
* @return float
*/
public function getPrice($currency = null)
{
$price = $this->get('price');
return $currency === null ? $price : $this->currencyConverter->convert($price, $currency);
}
}
Editoval Tharos (30. 9. 2014 21:20)
- akadlec
- Člen | 1326
@mkoubik pls můžeš tohle trochu rozvést?
pokud pracujete s entitou v presenteru (nedej bože v šabloně) tak to děláte blbě:
Mimo modelovou vrstvu se pracuje s identifikátory a view/DTO objekty. Pomocí nich se komunikuje se „services“ a „repositories“.
Editoval akadlec (30. 9. 2014 9:40)
- Azathoth
- Člen | 495
akadlec napsal(a):
@mkoubik pls můžeš tohle trochu rozvést?
pokud pracujete s entitou v presenteru (nedej bože v šabloně) tak to děláte blbě:
Mimo modelovou vrstvu se pracuje s identifikátory a view/DTO objekty. Pomocí nich se komunikuje se „services“ a „repositories“.
Ano, to by bylo velmi dobré rozvést. Co konkrétně je práce? Já
například z entity v šabloně i presenteru čtu a vůbec se za to
nestydím. Dělám to dobře nebo se nemá z entity ani číst?
Ovšem změny properties nechávám na modelu.
- mkoubik
- Člen | 728
Načítání dat z entit:
Já na to používám nějaké view-model objekty, které jsou (většinou) specifické pro danou šablonu a je jedno odkud se do té šablony dostanou. Může je třeba používat kodér pro jednoduché nasypání testovacích dat do šablony (třeba z neonu). Daly by se tím ty šablony testovat (i když to je asi blbost). Zkrátka to hezky definuje rozhraní mezi presenterem a šablonou.
Tyhle objekty pak vytahuju ze specializovaných repositories (které jsou většinou jednoduché a jen to mapují z entit). Výhoda je, že kodér může jakkoliv pracovat s daty v tom objektu (vnořené/opakované cykly, procházení vazeb, …) aniž by se bál o výkon. Jak efektivně všechno vytáhnout z db zase řeším já.
Ale je fakt, že u menších aplikací, nebo tam kde až tak nezáleží na izolaci šablon (různé „themes“, „skiny“ apod.) se dá ta entita použít i v šablonách (zvlášť pokud je píše programátor).
V Symfony je zvykem používat naopak hloupé předgenerované entity (gettery a settery) a pak musíš používat sandboxmode Twigu pokud chceš omezit co se dá s entitama v šabloně dělat.
Úprava dat
Tady je zase dobré mít nějakou doménovou vrstvu (zvlášť pro každou „doménu“) izolovanou (takže testovatelnou) od zbytku aplikace. To že ta vrstva používá doctrine a nějaké entity je zase implementační detail.
Já chci napsat něco jako
$this->orderProcess->confirmOrder($orderId)
a chci aby daná
služba udělala co potřebuju, případně vyhodila výjimku.
Tohle je nejjednodušší možné api, proč bych měl řešit jaký objekt použít jako parametr a kde ho vzít, když stačí nějaký unikátní identifikátor?
Editoval mkoubik (1. 10. 2014 9:40)