Komentáře k článku

Architektura aplikace nad Doctrine 2

Doctrine perex logo

O Doctrine 2 je na webu dostatek informací – i na Zdrojáku je poměrně podrobně popsáno, jak Doctrine používat, jak s ním pracovat a jak v něm psát i složitější úlohy. Tento článek ukáže nikoli samotný ORM, ale aplikaci, která jej používá, a možné problémy, na které při vývoji narazíte.

Zpět na článek

28 komentářů k článku Architektura aplikace nad Doctrine 2:

  1. Eda

    Díky

    Opravdu skvělý článek. Díky.

    Pokud by se našla chuť na nějaký další článek o praktickém postupu při používání Doctrine 2, budu minimálně já velmi rád :-)

  2. Vrtak-CZ

    Díky!

    Za ten cca rok co řeším problém odstínění Presenteru (Controlleru) od EntityManageru mám konečně jasno jak na to. Za celou tu dobu, po spoustě pokusů mě totiž vůbec nenapadlo postavit nad Repository a Service ještě jednu vrstvu.

    Zkusím to v praxi a uvidíme jak to dopadne (trochu se bojím aby to neskončilo u „God“ Facade objektů).

    Škoda že se do článku nevešlo vytvoření nového článku z formuláře. I když to už je asi trochu nad rámec (mapování dat z formuláře na entitu s reakcí na chyby). (možná by se to mohlo objevit v nějaké branch na githubu? :-P).

    1. manik.cze

      Re: Díky!

      ad „God“ Facade – ano, tam na to musíš dávat trochu pozor, nicméně Facade by měly být tak jednoduché, že u nich zas tak nevadí když je objekt větší. Nicméně nic tě nenutí pojmenovávat fasádu jako ArticleFacade a mít tam úplně všechno ohledně článků. Můžeš mít třeba ArticleAdminFacade, kde budou věcí pro editování a ArticleViewFacade, kde budeš mít věci okolo servírování článků apod.

      Jo, to se nevešlo, především proto, že jsem se kód snažil držet maximálně krátký (i tak jsem v něm byl schopný udělat dvě chybky). Ale možná zkusím něco vytvořit, nebo další článek :)

      1. Nox

        Re: Díky!

        Myslim že stejný problém je se service … ono už podle názvu ArticleService je jasné, že to asi bude mít na starosti milion různorodých činností s Articlem.

        Pokud těch akcí bude víc než málo, tak místo naházení všech akcí s jednou entitou do jedné třídy bych udělal třídy, které mají jednu specifickou oblast činnosti – třeba ArticlePublisher, ArticleRevision … tím lépe, pokud to bude pracovat na 2+ entitami

    2. Petr Procházka

      Re: Díky!

      Tak je to o tom jak facade budeš vytvářet. Když si z nich neuděláš god objekt tak nebudou :-). Když do nich přesouváš ‚modelovou‘ logiku z controlerů tak budou většinou menší než controlery.

  3. Petr Procházka

    Re: Architektura aplikace nad Doctrine 2

    Díky za dobrý článek.

    Píšeš:
    > „Jen zopakuji, že z výše zobrazených tříd se práce s perzistencí týká Facade, EntityManager, Repository.“

    Co je tímhle myšleno? Asi vnímáme ‚práci s perzistencí‘ každý jinak.
    Podle mě Facade by o ní neměli nic vědět a jen využívat api co nabízí EntityManager a Repository.

    1. manik.cze

      Re: Architektura aplikace nad Doctrine 2

      > Podle mě Facade by o ní neměli nic vědět a jen využívat api co nabízí EntityManager a Repository.

      No a EM a Repository pracují s perzistencí, tzn, sami jsou perzistencí také. Jde mi o to, že ve všech vrstvách, kde pracuješ s těmihle věci ať už přímo nebo zprostřdedkovaně, tak se ti na to budou psát hůř testy (mockovat Doctrine věci je peklo). Kdežto když to budeš mít oddělené, tak v pohodě vystačíš s unit testy.

      1. Petr Procházka

        Re: Architektura aplikace nad Doctrine 2

        Nepoužívám Doctrine takže mockování vrstvy které se v doctrine jmenuje EntityManager mi nedělá problém. Ale většinou při testování fasád mockuju až vrstvu níže (blíže k uložišti).

        Ale jestli tím bylo myšleno že fasáda pracuje z persitencí přes api EM/Repository tak je vše v pořádku a myslíme oba to samé :-)

    2. Jan Tichý

      Re: Architektura aplikace nad Doctrine 2

      Práce s persistencí je právě cokoliv, kde děláš s EntityManagerem nebo Repository. Pokud tedy někde potřebuješ zavolat find() nebo persist() nebo flush(), saháš si na persistenční vrstvu.

      Což je něco, co se v těch jednodušších a systémově ne moc čistých přístupech na začátku článku dělá v controlleru.

      V komplexním čistém přístupu na konci článku je to pak vyhozeno právě do fasády. Důležité pak v takovém případě je, že bu se žádný entity manager, flush, persist neměl objevit uvnitř žádného controlleru, entity ani servisy. Pokud například budeš v některých servisách flushovat, dříve či později ti to strašně nafackuje.

      1. jirkakoutny

        Re: Architektura aplikace nad Doctrine 2

        > Důležité pak v takovém případě je, že bu se žádný entity manager, flush, persist neměl objevit uvnitř žádného controlleru, entity ani servisy. Pokud například budeš v některých servisách flushovat, dříve či později ti to strašně nafackuje.

        Honzo mohl bys prosím více rozvést, proč přístup k persistentní vrstvě ze Service považuješ za extrémně špatné řešení? Chápu, že odstínění od persistence může zjednodušit testování Service a její znovupoužitelnost. Je ještě něco zásadnějšího, o čem byl měl vědět? Díky

        1. manik.cze

          Re: Architektura aplikace nad Doctrine 2

          > proč přístup k persistentní vrstvě ze Service považuješ za extrémně špatné řešení? Chápu, že odstínění od persistence může zjednodušit testování Service a její znovupoužitelnost. Je ještě něco zásadnějšího, o čem byl měl vědět?

          Podle mě je znovupoužitelnost (+ použitelnost) a testovatelnost alfa a omega OOP, takže je to ta nejzásadnější věc. Spíš je to tak, že pokud tyhle dvě věci (ona je to čistě teoreticky jen jedna, protože to spolu velmi úzce souvisí) nebudeš dodržovat, tak se může objevit celá řada dalších problémů, které by jinak vůbec nenastaly. Je to takový snowball effect – například zavedení jedné statické metody kdovíkde se dost často podepíše na x místech.

          1. Opravdový odborník :-)

            Re: Architektura aplikace nad Doctrine 2

            Neznám PHP a jeho knihovny, třeba je v tom nějaká záludnost, ale: Jaký je rozdíl mezi tím, jestli budu mockovat EM nebo jestli budu mockovat nějakou svoji třídu (dodatečno vrstvu)? Proč by jedno mělo být testovatelné a druhé ne?

            A co se týče znovupoužitelnosti a přenositelnosti – EM by sám o sobě měl poskytovat dostatečnou abstrakci. Např. v Javě máme JPA a k němu různé implementace – můžu změnit implementaci ORM, ale rozhraní je stejné (tudíž není potřeba měnit aplikaci). To v PHP nemáte?

            1. Nox

              Re: Architektura aplikace nad Doctrine 2

              Máme, existuje interface ObjectManager, který má implementace EntityManager a DocumentManager – takže by to mělo jít

  4. Teuzz

    new DateTime

    To new DateTime mě pěkně tahá za oči, parametrem nebo DI by to bylo hezčí.

    Ale pěkný iterativní článek, který rozešlu na všechny strany, abych se nemusel dívat na to, na co se dívat musím :) Díky, Vašku!

    1. manik.cze

      Re: new DateTime

      Jo, jasně, new DateTime by tam napřímo být nemělo, nicméně jsem chtěl zdroják co nejvíc zjednodušit v ohledu k tématu článku. Už tak myslím, že to je bezkonkurenčně nejdelší článek, co tu je :)

  5. Jakub Vrbas

    Bazba na Doctrine/ORM?

    Díky za článek.
    Jen bych se chtěl zeptat, jak těsná je podle vás vazba na Doctrine, resp. na ORM obecně? Já tam tu vazbu totiž moc silnou nevidím a dokážu si dost dobře podobnou architekturu představit i bez ORM vrstvy.

    1. manik.cze

      Re: Bazba na Doctrine/ORM?

      Vazba nijak těsná není, naopak ta architektura je tak volná, že pokud nahradíte Entity Manager obdobnou částí jakékoli jiné perzistetntí vrstvy (pokud je napsaná alespoň trochu slušně), tak by absolutně neměl být problém. Nicméně nechtěl jsem článek pojmout takto obecně, ten kdo tuší, tak si to tam právě najde :)

  6. jirkakoutny

    2 dotazy

    Vašku, moc díky za supr článek. Přečetl jsem ho už 3x a pomohl mi domyslet naši vlastní architekturu (nepoužíváme Doctrine).

    2 otázky:

    1) Předpokládám, že v Mediu děláte spíše složitější aplikace. Oddělujete tedy vždy „povinně“ všechny vrstvy, tj. Facade, Service, Repository a Entity Manager? Nebo vrstvy přidáváte až „za běhu“ s tím, jak aplikace roste? Z článku pro mě vyplývá to první (možná až na nepovinné Service).

    2) Do jaké vrstvy patří podle tebe Entity cache? Honzův 2 roky starý článek o pěti vrstvách modelu ji dával do Repository. Dáváte ji do Repository i nyní? Pro unit testy pak cache nějak centrálně vypínáte?

    1. manik.cze

      Re: 2 dotazy

      1) Všechny vrstvy nepoužíváme, resp. na poslední krok, tj. zavedení Facade právě u některých přecházíme. Vrstvy za běhu přidávat lze v pohodě, viz článek, nicméně se ukazuje, že to chce odhadnout víceméně správnou úroveň už na začátku…a vůbec není na škodu, pokud se zvolí vyšší dekompozice než nižší (protože projektové zadání spíš bobtná než naopak). Takže za mě viz shrnutí myšlenek v závěru článku – u projektů, které povedu já budu určitě vyžadovat spíše větší dekompozici – kódu se ve výsledku píše skoro stejně a přitom je mnohem lépe čitelný (a vzhledem k velikosti projektu je mi fakt jedno, jestli je na danou věc jedna třída nebo dvě – spíš naopak, stejně se to pak většinou dělí, aby nebyly tak obrovské, viz sousední diskuze s Patrikem).

      2) Nevím, co přesně myslíš Entity cache – jestli je to Identity Map (tzn. aby se jedna entita během jedné transakce nestahovala z DB vícekrát), tak to u nás obhospodařuje Doctrine. A v jiných nástrojích persistenci bych to opravdu opět asi zařadil do Repository (resp. pro něco společného, co využívá jak Repository, tak EntityManager, protože to jsou informace, které potřebují obě tyhle věci). Pokud myslíš jinou cache, tak řekni přesněji kterou – cachovat se v téhle architektuře dá na hodně místech. Pro unit testy nic nevypínáme – unit testy jako takové nemají mít s perzistentní vrstvou nic společného (pak to jsou integrační testy) a unit testů se snažíme mít většinu – tzn. většina logiky by měla být v částech bez perzistence, právě proto, aby se dala dobře unit testovat. V integračních testech – tj. tam kde se mimo jiné používá databáze, tak to většinou používáme tak jak to je – v Identity Map by měl být čerstvý obraz databáze. Teoreticky v Identity Map může zůstat viset pozměněná instance z jiného testu, nicméně pokud se testy píší pořádně (tj. pracují jen s daty, které si samy zakládají), tak by s tím problém být neměl. Ono pokud by chtěl člověk opravdu zajistit správnou izolovanost testů, tak by měl pro každý jednotlivý test spouště úplně celou aplikaci znovu. Tak jak to máme my, tak je to spíš podobné reálnému využití těch tříd/metod, takže paradoxně to, že tam ta izolace v tomto kontextu není dokonalá, tak pokud by tam někdo nějaké to flush a podobně zapomněl, tak na to možná díky tomuhle spíš přijdeme – nebo že nejsou ideálně napsané ty testy.

      > Pro unit testy pak cache nějak centrálně vypínáte?
      V unit testech bys neměl mít potřebu nikdy nic vypínat/zapínat a už vůbec ne centrálně. V integračních viz výše.

      1. jirkakoutny

        Re: 2 dotazy

        Ad ta cache. Konkrétnější příklad:

        Entity User má atribut např. hasVisitedDashbo­ard. Ten se nastaví na true poprvé, když si daný uživatel zobrazí stránku „Dashboard“. Podle tohoto příznaku pak uživateli něco skrýváme nebo naopak zobrazujeme a musíme tak znát hodnotu toho příznaku na několika dalších stránkách.

        Hodnotu příznaku uchováváme v databázi. Samozřejmě se nám ji ale nechce načítat z databáze při každém načtení stránky, proto ji máme také v Memcached/File­cache.

        V jaké vrstvě by se tedy tohle mělo podle tebe řešit? Díky

        1. Michal Augustýn

          Re: 2 dotazy

          Jestli do toho můžu vstoupit, tak dle mého názoru toto náleží do Repository. Otázka je, jak to implementovat.

          1) Můžeš to naprasit přímo do originální Repository.

          2) Uděláš navíc implementaci interfejsu Repository, říkejme ji „cache repository“. Ta bude zkoušet sahat do cache (třeba memcached) a pokud to v cache nebude, tak zavolá metodu na původní Repository.

          3) V „ideálním“ řešení budou rovnou tři implementace rozhraní Repository. Jedna bude klasická implementace přímo přistupující do databáze (stejně jako v možnosti 2), druhá bude natvrdo přistupovat ke cache, a konečně třetí implementace bude komponovat chování předchozích dvou Repositories. Tj. zkusí zavolat GetById na druhé (cache) Repository a pokud nic nevrátí, zavolá tutéž metodu na první (přímé) Repository.

          Volba záleží na konkrétní situaci, ale vždy bych se snažil dostat minimálně na úroveň možnosti 2.

          1. manik.cze

            Re: 2 dotazy

            Ohledně implementace a interfaces souhlasím s Augim. Akorát si nejsem jistý, jestli to vlastně má být až zas tak Repository – problém je ten, že ta cache se v té repository bude používat pro čtení, ovšem už si nemyslím, že je záležitostí Repository, aby tu cache nějak invalidovala. Takže jedině to dělat někde jinde (buď někde „automaticky“ na pozadí, což mi přijde wtf a implementace bude asi fuj) a nebo to musí někdo dělat.

            S tím souvisí moje druhá poznámka a to, že podle toho co píšeš mi tahle úroveň cache moc nedává smysl. Tohle je informace, kterou předpokládám potřebujete právě jen když je uživatel přihlášený, proto dle mého patří do „CurrentUser“, resp. jeho Identity parametrů (nejmenuju nějaké konkrétní třídy, jen přibližně pro představu), kde bude dostupný společně s dalšími parametry podobného charakteru.

            Osobně radši používám cache spíš na vyšších úrovních než na nižších. Čím nižší, tím více oblastí to ovlivní a tím větší problém je cache zpětně doimplementovat (pak se všude, kde se původně používala ta původní třída musí člověk zamyslet, jestli tam bude chtít ty cachované výsledky nebo normální apod.).

          2. bene

            Re: 2 dotazy

            ad1) fuj :-)

            ad2) Dekorátor

            ad3) Při „výcenásobné“ cache by skládací třída pouze „emulovala“ dekorátor. Řekněme že máme databázi, file cache, local variable cache.

            Dekorátor:
            $rep = new LocalVariable­ArticleReposi­tory(new FileArticleRe­pository(new DatabaseArticle­Repository());
            $rep->find();

            Skládací třída:
            $rep= ArticleCacheRe­pository();
            $rep->add(new DatabaseArticle­Repository());
            $rep->add(new FileArticleRe­pository());
            $acr->add(new LocalVariable­ArticleReposi­tory());
            $rep->find(); // zde by musela byt logika, ktera prochazi jednotlive registrovane repository a vraci vysledek, uklada do cache, atp

  7. jirkakoutny

    Zodpovědnost Facade

    Ještě jedna věc:

    Má Facade povoleno pracovat jen s entitami, nebo může využívat i jiné třídy? Konkrétní příklad:

    Z POSTu mi přijde URL stránky, kterou chci uložit do databáze. Před uložením si ale k ní chci stáhnout ještě její titulek (obsah HTML značky <title>). Patří stažení titulku do Facade nebo do Controlleru?

    1. možnost http://pastebin.com/mD5RCc8P
    2. možnost http://pastebin.com/MctbikJy

    Díky

    1. manik.cze

      Re: Zodpovědnost Facade

      Preferoval bych asi variantu č.1. V té druhé zbytečně přenášíš konkrétní další zodpovědnosti na Controller, které tam imho být nemusí. Je to vždycky těžké, jestli více starostí nechat na „klientovi“ nebo toho víc udělat uvnitř metody, univrzální recept jsem na to ještě nenašel. Čím víc toho necháš na klientovi, tím máš samozřejmě obecnější metodu. Řešením může být nabídnout jak obecnější, tak konkrétnější metodu.

      Ostatně tu tvojí metodu bych určitě taky rozdělil (i když je to dělení trochu v jiném smyslu než píšu v předchozím odstavci). V první bych nechal stahování a nalejvání dat do entity, vrátí neperzistovanou entitu. V druhé teprve práci s perzistencí a využití první metody. U té první metody bych právě uvažoval, jestli by se nehodila do té servisní vrstvy, o které píšu ve článku – záleželo by na konkrétní implementaci. Nicméně pokud ji nemáš, tak dvě metody ve fasádě jsou fajn.

    2. manik.cze

      Re: Zodpovědnost Facade

      Ještě jsem zapomněl na odpověd na původní dotaz:
      > Má Facade povoleno pracovat jen s entitami, nebo může využívat i jiné třídy?

      Rozhodně to má povolené, ostatně viz návrhový vzor Facade, který tuším v článku taky někde odkazuji.

      http://en.wikipedia.org/wiki/Facade_pattern
      Schovává používání složitějšího systému za volání jednoduchých metod.

  8. JaSHin

    Co vlastně vrací service
    Zdravím,

    není mi moc jasné co vrací service. Kdybych nemapoval a nechával entity prosakovat až do presenteru, tak co by vracela konkrétní service, která najde TOP10 článku. Stejně to musí do něčeho přemapovat. Nebo se pletu ?

Napsat komentář

Přihlásit se

Tato diskuse je již příliš stará, pravděpodobně již vám nikdo neodpoví. Pokud se chcete na něco zeptat, použijte diskusní server Devel.cz

Zdroj: http://www.zdrojak.cz/?p=3611