NetteDatabase vs. Doctrine – klady zapory, zkusenosti

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
besir
Člen | 170
+
+1
-

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
+
0
-

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
+
0
-

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
+
0
-

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

hrach
Člen | 1838
+
+8
-

Ja bych použil nextras/ORM. Prý dnes @hrach ve vlaku začal psát dokumentaci.

Lkopo
Člen | 65
+
+1
-

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
+
0
-

@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)

Azathoth
Člen | 495
+
0
-

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é.

David Ďurika
Člen | 328
+
+4
-

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
+
+6
-

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
+
+1
-

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
+
0
-

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
+
+5
-

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
+
0
-

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
+
0
-

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
+
0
-

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!

Tharos
Člen | 1030
+
0
-

Filip Procházka napsal(a):

V Doctrine 2.5 přibyla „Second Level Cache“ která bude úplně jiný kafe a s rychlostí aplikací brutálně zahýbe :)

Oukej! :) Jsem zvědavej, protože jestli to tak dopadne, tak je to pro Doktrínu výborná zpráva.

hrach
Člen | 1838
+
+5
-

@FilipProcházka korektni by bylo zminit, ze to vyzaduje redis, memcache, nebo apc(u), coz moc casto na hostingu nenajdes ;-)

Zax
Člen | 370
+
0
-

@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
+
+2
-

@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
+
0
-

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?

Lkopo
Člen | 65
+
0
-

Aké väzby máš na mysli? Ak nechceš aby nejaká entita nebola zmapovaná a vložená do DB, nepoužiješ anotáciu @ORM. Teraz neviem, či za entitu pokladáš celú tabuľku alebo len nejaký stĺpec.

Editoval Lkopo (21. 9. 2014 17:11)

David Matějka
Moderator | 6445
+
0
-

@Š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

akadlec
Člen | 1326
+
0
-

Btw a co takové spojení doctrine a couche? Když jsem se podílel na jednom projektu tak tam tohle spojení bylo. Pravda, přináší to pár komplikací ale zase to bylo vyváženo rychlou odezvou data které se netahaly z mysql ale právě z couche.

Šaman
Člen | 2666
+
0
-

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ší.)

hrach
Člen | 1838
+
0
-

@Šaman ještě to takto děla Nextras\ORM (ta nezavislost entit).

@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.

Tharos
Člen | 1030
+
+1
-

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
+
0
-

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
+
0
-

@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
+
+6
-

@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)

Zax
Člen | 370
+
+1
-

@Tharos Hehe tak to je fajn :-) No a co ty víš, třeba s Doctrinou jednou fakt narazím a ještě rád se poohlédnu po něčem lehčím, takže za mě palec nahoru.

Filip Procházka
Moderator | 4668
+
+1
-

@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
+
0
-

@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
+
0
-

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
+
0
-

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
+
+2
-

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
+
+2
-

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
+
0
-

@mkoubik doufám že se konečně potkáme na této posobotě a nezapomeneme (zase) si o tomhle pokecat :)

Zax
Člen | 370
+
+2
-

Tak tohle budu asi ještě hodně dlouho vstřebávat. Každopádně děkuji za podnět, možná tak za deset let se naučím psát trochu čistě.

Tharos
Člen | 1030
+
+2
-

@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
+
0
-

@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
+
0
-

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.

akadlec
Člen | 1326
+
0
-

Měl by se tedy na to používat nějaký Adaptér? Kterému by se předala entita a ten by pak zpřístupnil její hodnoty presenteru/šabloně?

mkoubik
Člen | 728
+
0
-

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)