Patrik Votoček

weBlog



Service - odstínění Presenteru a Komponent od EntityManažeru


předchozích dvou článcích jsem se snažil dopátrat, jak kdo implementujete modelovou Service vrstvu pro Doctrine ORM 2 v Nette Frameworku. Dlouhodobě totiž hledám ideální řešení, protože jsem se svým stávajícím lehce nespokojen. Ono funguje a je dokonce i celkem ohebné a použitelné ve spoustě případů a situací. Nicméně stále mám pocit, že je tam něco špatně a tak jsem se vydal pátrat, co to je.

Vzal jsem tedy své stávající řešení a našel dva body, které mě trápí nejvíce. Jak se ale v komentářích ukázalo, tyto dva body spolu úzce souvisí (ještě více, než jsem se původně domníval), proto se na ně nedá koukat odděleně. Díky mému oddělení a snaze soustředit se opravdu jen na ony dva problémy došlo k informačnímu šumu, který bych chtěl v tomto článku napravit.

První chybou bylo, že jsem se v článcích zmiňoval spíše o celé service vrstvě, i když mi ve skutečnosti šlo spíše o service třídu.

Základní dělení service vrstvy

Service vrstvu jsem – už když jsem s doctrine začínal – začal dělit na dvě části. A to na část, která načítá data a část, která s daty manipuluje. Část, která načítá data je v doctrine ve své podstatě již vyřešena. Representuje ji Repository a je poměrně pevně svázaná s entitou (nechme stranou, jestli je to dobře – je to funkční řešení a zatím jsem nenarazil na případ, kdy by to vadilo). Nicméně část, která se stará o manipulaci s daty v doctrine ve své podstatě, chybí (ano, můžete komunikovat přímo s entity manažerem, ale je to takové – no vždyť víte). A tohle je ta část, kde přichází na scénu Service třída, kterou je potřeba vymyslet.

Service třída

Stávající implementace jsou tedy metody:

  • create – kde prvním parametrem je pole, kde klíč odpovídá property v entitě a hodnota je její hodnotou
  • update – kde prvním parametrem je entita a druhým parametrem je pole (stejné jako v prvním případě)
  • delete – kde prvním je entita

Jak to celé funguje? Vytvoří se instance Service, jako služba v konstruktoru dostane instanci entity manžeru a fullclass entity, které se týká. Při zavolání create se spustí createEntityPro­totype (vytvoří prázdnou instanci entity – u entit nepoužívám „parametrizované“ konstruktory). Poté se zavolá fillData (naplní entity hodnotami z pole). Pak se vytvořená entita připojí k entity manažeru a pokud nebylo řečeno jinak (poslední parametr metody), tak se změny flushnou a vrátí se hotová entita. Podobně fungují i ostatní metody.

Honza Tichýkomentářích napsal: Svazuje Tě to v možnosti mít pro jednu entitu víc různých servisů, či naopak mít servis pracující s více entitami najednou.. To byla věc, nad kterou jsem si dlouho lámal hlavu a stále trochu lámu. Jako příklad uvádí identitu a různé způsoby autentizace.

Tento příklad řeším tak, že mám entitu Identity a entitu Credentials (do budoucna je v plánu entita OAuth , jako další způsob přihlašování). Na načítání těchto dat není nic zajímavého. Zajímavější bude registrace nového uživatele.

Než bych složitě popisoval co a jak, raději ukážu kód:

class CredentialsService extends \Nella\Doctrine\Service
{
        public function create(array $values, $withoutFlush = FALSE)
        {
                $service = $this->getContainer()->getService('IdentityEntity');
                $values['identity'] = $service->create($values, TRUE);
                return parent::create($values, $withoutFlush);
        }
}

$values = array( /* v takovémto formátu dostanu data (třeba z formuláře) */
        'username' => "vrtak-cz",
        'password' => "tajneheslo",
        'email' => "muj@email.cz",
        'dispalyName' => "Patrik Votoček",
);

$service = $form->getDoctrineContainer()->getService('CredentialsEntity');
$entity = $service->create($values); /* vrátí naplněnou instanci CredentialsEntity s asociací na vyplněnou IdentityEntity */

Věc, která se mi nelíbí, je „service locator“ přístup ke službě obstarávající IdentityEntity. Pokud bych ale tuto službu vstřikoval v konstruktoru služby obsluhující CredentialsEntity , tak by se například u mazání vytvářela naprosto zbytečně.
Tak to je tedy důvod, proč jsem nechápal – a v podstatě stále nechápu – obsluhu více entit jednou service nebo naopak jedné entity pomocí více service.

Posledních pár dní přemýšlím, jak to vylepšit a hlavně, jak zmiňuje Honza a Filip, zda vůbec a jak se zbavit $entityClass v service třídě.

5 komentářů


Karel Hák

nový

Není závislost na $entityClass v servisní třídě způsobena tím, že vní jsou věci, které tam být nemají?

Koukal do tvé implementace (kterou jsem se zprvu inspiroval), tak mi přijde, že metody update, delete, create tam prostě nepatří. To jsou operace, které se podle mně hodí spíše do repository (a je to tak uvedeno i v článku 5 vrstev modelu od Honzy Tichého).

Já to chápu tak, že do servisní vrstvy už podle mně entity manager nepatří (a už vůbec ne nějaké zpracování PDO vyjímek), ale pouze do nich pošlu repository, s kterými potřebuje pracovat. Každopádně s Doctrine teprve začínám, takže si rád vyslechnu proti argumenty, pokud si budete myslet, že co jsem napsal je blbost :)

Karel Hák avatar

Patrik Votoček

nový

Pokud se bavíme o Repository tak jak ji popisuje Honza ve svém článku pak máš pravdu. Pokud se ale bavíme o Repository jako patternu, tj tak jak ji já používám. Pak pravdu nemáš, protože save / update / delete nemají u repository co dělat (viz. také komentář pod Honzovým článkem http://www.phpguru.cz/…rstev-modelu#…).

Pokud to shrnu tak u mě DAO je reprezentováno dvěma třídami. Jednou z nich je Repository (READ) a druhou Service (CREATE, UPDATE, DELETE).

Patrik Votoček avatar

HosipLan

nový

[1] Podle mě to blbost není. Sdílím úplně stejný pohled na věc. Jenom mi bylo blbé to psát po třetí, ke třetímu článku v sérii :)

HosipLan avatar

Patrik Votoček

nový

Já ale neříkám že to je blbost :-). Já jenom říkám že to tak nepoužívám a proč to tak nepoužívám.

Patrik Votoček avatar

mbskot

nový

Zdravim, nakoniec teda akym sposobom sa dostavas k repositorom? Mas v kazdej takejto sluzbe (alebo v baseService) getRepository()?

mbskot avatar

Přidej komentář



cenzuruje váš poskytovatel připojení?

Kategorie

Čtu

Kamarádi