tiny ‘n’ smart
database layer

Odkazy: dibi | API reference

Oznámení

Omlouváme se, provoz fóra byl ukončen

LeanMapper – kompletní modelová vrstva a transakce

před 6 lety

Filip111
Člen | 244

Trochu se trápím s rozvrstvením modelové vrstvy, tak by mě zajímalo, jak to řešíte vy.

Jednoduchý příklad – odešlu formulář a následně z jeho hodnot vytvořím entitu User a jeho role (m:hasMany) UserRole.

Ideláně si to představuji tak, že vytvořím entitu User, do vazby jí nastavím role UserRole a předám celý vytvořený pavouk entit nějaké fasádě, která to celé uloží. I za cenu, že bude muset použít více repository.
Jenže to v LM nejde, protože dokud není entita uložená, nemůžu ji spojit vazbou s jinou entitou. (nevím přesně chybu, něco s detached entity…)
(v Doctrine2 by to šlo, tam se entita dokáže persistovat jestli se nepletu)
Jak a kde tedy vše uložit?

Požívám nad repository ještě fasády, ale zjišťuji, že asi ne úplně ideálně. Obvykle vytvořím entitu už v presenteru na základě dat z formuláře, předám ji fasádě, ta ji přes repository uloží a provede další navazující akce (pošle maily, něco zaloguje apod.). Pro práci s jednou entitou s tím nemám problém.

Jenže v případě ukládání více provázaných entit, narážím právě na výše zmíněný problém. Řeším to tak, že v presenteru vytvořím několik entit a každou přes svojí fasádu/repository uložím a průběžně jak je ukládám je spojuji dohromady.
Výsledkem jsou bobtnající presentery a hloupé fasády.

Co s tím?
Pokud budu vždy do fasády předávat data z formuláře, tak se mi ztrácí znovupoužitelnost konkrétní metody ve fasádě a i když přesunu spoustu kódu do fasády nic dalšího mi to nepřinese.
Naopak pokud budu fasádě předávat jednu entitu, budu mít pořád veškerou logiku v presenteru, což je taky špatně.

Související otázka – jak a v jaké vrstvě řešíte transakce?
Opět bych zase vidět ideální způsob předat celý pavouk Entit nějaké fasádě, která v rámci jedné transakce vše uloží. Jenže to nejde.

Díky, Filip

ps.: u malé jednoúčelové aplikace je to celkem jedno, ale jak se rozrůstá začíná mě to pálit stále víc

Editoval Filip111 (31. 12. 2013 9:27)

před 6 lety

Tharos
Člen | 1042

Ahoj,

jak by se Ti líbilo cca takovéto řešení Tebou uvedeného příkladu?

/**
 * Simple proxy to transactions
 */
class Transaction
{

    /** @var LeanMapper\Connection */
    private $connection;


    /**
     * @param Connection $connection
     */
    public function __construct(Connection $connection)
    {
        $this->connection = $connection;
    }

    /**
     * @param string|null $savepoint
     */
    public function begin($savepoint = null)
    {
        $this->connection->begin($savepoint);
    }

    /**
     * @param string|null $savepoint
     */
    public function commit($savepoint = null)
    {
        $this->connection->commit($savepoint);
    }

    /**
     * @param string|null $savepoint
     */
    public function rollback($savepoint = null)
    {
        $this->connection->rollback($savepoint);
    }

}

class Users
{

    /** @var Transaction */
    private $transaction;

    /** @var UserRepository */
    private $userRepository;

    /** @var UserRoleRepository */
    private $userRoleRepository;


    /**
     * @param Transaction $transaction
     * @param UserRepository $userRepository
     * @param UserRoleRepository $userRoleRepository
     */
    public function __construct(Transaction $transaction, UserRepository $userRepository, UserRoleRepository $userRoleRepository)
    {
        $this->transaction = $transaction;
        $this->userRepository = $userRepository;
        $this->userRoleRepository = $userRoleRepository;
    }

    /**
     * @param User $user
     * @param UserRole[] $userRoles
     */
    public function createUser(User $user, array $userRoles = array())
    {
        $this->transaction->begin();
        try {
            foreach ($userRoles as $userRole) {
                $this->userRoleRepository->persist($userRole);
            }
            $user->addToRoles($userRoles);
            $this->userRepository->persist($user);
            $this->transaction->commit();
        } catch (\Exception $e) {
            $this->transaction->rollback();
            throw $e;
        }
    }

}

Editoval Tharos (31. 12. 2013 10:55)

před 6 lety

Filip111
Člen | 244

Třída Transaction se mi líbí – nechtěl jsem používat v presenteru connection, takže tohle je asi ok.
Nemusim mít výčitky, když to použiju v repository nebo i presenteru, protože už je to zapouzdřený.

Třída User je přesně to nad čím si lámu hlavu, jestli používat či nikoliv. Dokud jsem nepoužíval ORM tak jsem podobný objekty používal. S tím jak jsem ale začal používat ORM, tak jsem začal používat entity.

S čím pak pracuješ např. v presenteru/šabloně? S entitou User nebo s objektem User, který jsi uvedl?
(nebo je entita User a objekt User to samé, jen jsi ořezal deklarace properties?)
Jakou zodpovědnost by měla mít třída User? Jen uložení celého pavouka entit souvisejících s uživatelem nebo i jejich čtení a předávání dál?

Občas mi tam právě takhle mezivrstva chybí, ale nevim jestli to zapouzdření nebude už moc přehnaný.
Entita, Repository, Objekt (např. User), Fasáda (pracující někdy s entitou někdy s objektem User) (?)

Vrstvu s fasádou bych chtěl ponechat, protože mi přijde ideální právě na nějaký činnosti, které už nemusí být v transakci a které nějak souvisí s např. vytvořením uživatele. Tedy právě zaslání mailů, případně práci napříč dalšími službami.

Ty to takhle používáš nebo jsi jen vytvořil nějaký příklad?

Díky

před 6 lety

Pavel Macháň
Člen | 285

Filip111 napsal(a):

Třída Transaction se mi líbí – nechtěl jsem používat v presenteru connection, takže tohle je asi ok.
Nemusim mít výčitky, když to použiju v repository nebo i presenteru, protože už je to zapouzdřený.

Třída User je přesně to nad čím si lámu hlavu, jestli používat či nikoliv. Dokud jsem nepoužíval ORM tak jsem podobný objekty používal. S tím jak jsem ale začal používat ORM, tak jsem začal používat entity.

S čím pak pracuješ např. v presenteru/šabloně? S entitou User nebo s objektem User, který jsi uvedl?
(nebo je entita User a objekt User to samé, jen jsi ořezal deklarace properties?)
Jakou zodpovědnost by měla mít třída User? Jen uložení celého pavouka entit souvisejících s uživatelem nebo i jejich čtení a předávání dál?

Občas mi tam právě takhle mezivrstva chybí, ale nevim jestli to zapouzdření nebude už moc přehnaný.
Entita, Repository, Objekt (např. User), Fasáda (pracující někdy s entitou někdy s objektem User) (?)

Vrstvu s fasádou bych chtěl ponechat, protože mi přijde ideální právě na nějaký činnosti, které už nemusí být v transakci a které nějak souvisí s např. vytvořením uživatele. Tedy právě zaslání mailů, případně práci napříč dalšími službami.

Ty to takhle používáš nebo jsi jen vytvořil nějaký příklad?

Díky

Já osobně bych vnitřek třídy Users měl přímo v UserFacade. Nevidím důvod to mít takhle mimo, přeci jen od toho je tu ta fasada aby to cele obalila a v prezenteru si po nějaké akci vyvolal createUser($name, $username,…) a dostal si treba jen true|false jako odpověď pro prezenter.

před 6 lety

Filip111
Člen | 244

@EIFEL:
Tzn. ty bys třídu User viděl jen jako pomocnou pro uložení Usera se všemi vazbami s souvisejícími entitami? Ve zbytku systému bys použil klasicky entity/fasády?

To se mi moc nelíbí – když už třídu User zakládat, tak by měla mít širší využití než jen jako berlička pro založení uživatele. Pak by se struktura modelu ještě víc rozštěpila – pro založení bych používal třídu User, pro editaci/čtení už jen fasádu která pracuje s entitami.

před 6 lety

Tharos
Člen | 1042

@Filip111:

Třída Transaction se mi líbí – nechtěl jsem používat v presenteru connection, takže tohle je asi ok.
Nemusim mít výčitky, když to použiju v repository nebo i presenteru, protože už je to zapouzdřený.

Je to klasická proxy třída. Takové se obzvláště hodí při práci s různými god-objekty. :)

Třída User je přesně to nad čím si lámu hlavu, jestli používat či nikoliv. Dokud jsem nepoužíval ORM tak jsem podobný objekty používal. S tím jak jsem ale začal používat ORM, tak jsem začal používat entity.

Pozor, to není User, ale Users. :) To je ta fasáda. Samozřejmě by v reálu obsahovala i další metody podle potřeby, já jsem jen chtěl být ve své ukázce stručný a jít k jádru věci. Já do názvu „fasádních“ tříd slovo Facade nedávám, pojmenovávám je ala Users, Orders, Books atp.

S čím pak pracuješ např. v presenteru/šabloně? S entitou User nebo s objektem User, který jsi uvedl?
(nebo je entita User a objekt User to samé, jen jsi ořezal deklarace properties?)
Jakou zodpovědnost by měla mít třída User? Jen uložení celého pavouka entit souvisejících s uživatelem nebo i jejich čtení a předávání dál?

Přestal jsem používat Nette presentery :–P (bylo by to na delší povídání), ale v té části aplikace, která má na starosti to, k čemu se běžně používají presentery, pracuji s entitami a standardně s těmi „fasádními“ třídami. Je-li aplikace jednoduchá (anebo se mi doménovou logiku podaří elegantně umístit přímo do entit), tu fasádní vrstvu vynechávám a pracuji přímo s konkrétními repositáři. Pokud bych pak potřeboval v takové aplikaci bez fasádní vrstvy vyřešit to, co čem jsi psal, na ten konkrétní úkon bych si nějakou „servisní“ třídu vytvořil. Mé názvosloví (servisní vs. fasádní…) je kompatibilní s tímto článkem.

Ty to takhle používáš nebo jsi jen vytvořil nějaký příklad?

Já se hodně rozhoduji podle konkrétní aplikace. Velmi jednoduché moc neprožívám a pracuji v „presenterech“ přímo s repositáři. Doménovou logiku se vždy snažím mít co nejvíc v entitách (v duchu DDD), model se snažím mít co nejméně „anémický“. To, co se mi ale do entit v takové aplikace nepodaří vměstnat (nebo vhodným způsobem do repositářů), řeším pomocí berliček v podobě servisních tříd.

Je-li aplikace trochu košatější, snažím se zavést plnohodnotnou fasádní vrstvu, jejíž třídy vytvářejí API modelu. Pak už samozřejmě v „presenterech“ nepracuji přímo s repositáři, ale s těmi fasádními třídami. Používám konkrétní repositáře (na rozdíl od například Kdyby\Doctrine) a query objety (hodně podobně, jako Filip v Kdyby\Doctrine, jen s tím logickým rozdílem, že mé query objekty se překládají do sekvence volání nad instancí Fluent).

Proxy třídu pro transakce používám.

Editoval Tharos (2. 1. 2014 9:50)

před 6 lety

Filip111
Člen | 244

@Tharos:
Teď už mi to konečně dává smysl – vlastně jsem jsem to vyřešil (ještě před založením tohoto topicu) stejně a předal do fasády, jednotlivé entity k uložení. Budu se toho držet i nadále.

Díky za odpovědi.

Ještě mě zaujala zmínka o presenterech nepresenterech :). Neměl bys chuť o tom ještě něco napsat?
Zajímalo by mě, jestli důvodem byla znovupoužitelnost nebo nějaká omezení presenterů, případně co ti to přineslo za výhody (asi v novém topicu). Ale nechci tě zbytečně zdržovat.

před 6 lety

Šaman
Člen | 2275

Ohledně těch presenterů jsem se chtěl taky zeptat, ale pak jsem to neudělal, protože naprosto klíčové k životaschopnosti LM je vytvořit dokumentaci, což může udělat jen Tharos. Takže se zeptám až potom :)

Lehce offtopic: @Tharosi plánuješ přijít na lednovou posobotu? Vypadá to, že bude zajímavá. A ve volném čase bychom mohli nad pivem probrat pár dotazů na LM.

před 6 lety

Filip111
Člen | 244

@Tharos, Šaman:
souhlas, nejdřív dokumentace, zeptáme se potom :)

před 6 lety

Tharos
Člen | 1042