Návrhový vzor pro entity při 5ti vrstvém modelu
- hAssassin
- Člen | 293
Ahoj,
vím, že to možná není dotaz přímo na Nette, ale v poslední době se
Nette stalo synonymem čistého návrhu, proto bych se rád zeptal zde. O co
jde. Snažím se vymyslet model a snažím se jít cestou 5ti vrstev. Takže
mám nějaké services, repositories, mappery a entity. Vrstva pro DB je Dibi,
ale to je celkem jedno, mě jde teď především o entity a jaký návrhový
vzor na ně použít.
Myslím si, že by sama entita neměla být pouze obálkou nad daty, ale
kromě validace dat by měla poskytovat i nějaké základní API. Vycházím
z toho, že mám tři základní typy entit, které od sebe dědí:
BaseEntity
(ta má pouze ID), BaseListEntity
(ta
přidává pořadí a metody pro výběr předchůdce/následníka v seznamu,
případně zjištění, zda je entita první/poslední v seznamu) a konečně
BaseTreeEntity
(ta má samozřejmně rodiče, potomky a metody pro
zjištění zda je kořen, list, apod.).
Do teď celkem v pohodě, pracuje se s tím pěkně, dá se to udělat lazy
(např. zjištění rodiče). Co kdybych však chtěl typů entit více? Např.
takové entity, které půjdou aktivovat/deaktivovat, lokalizovat, nebo např.
publikovat? Vycházejme z té aktivace/deaktivace (ta je celkem jednoduše
představitelná – taková entita bude mít navíc property
$active
a metody isActive()
a
setActive()
). Pokud budu mít entit, které půjdou aktivovat
20 různých typů, musel bych 20krát tyto metody implementovat, nebo
implementaci vyčlenit do nějaké rodičovské třídy. Co když ale bude
dalších 20 typů entit, které implementovat nepůjdou? A jak to zapojit
mezi BaseEntity
, BaseListEntity
a
BaseTreeEntity
? Pak bych musel mít vždy navíc
BaseActivableEntity
, BaseListActivableEntity
a
BaseTreeActivableEntity
. A pokud do toho zapojíme i další
výše zmíněné typy (lokalizovatelné/publikovatelné entity) tak to
začíná být poněkud nepřehledné. Snad je to srozumitelné.
A jak z toho tedy ven? Pokud tedy uvažuji že není povolená vícenásobná dědičnost a hlavně pokud používám PHP 5.3 (nevím jestli by to nešlo obejít pomocí Traitů, zatím jsem na 5.4 nepovýšil). Existuje nějaký návrhový vzor který by toto řešil? Napadly mě adaptéry, které se ale sem moc nehodí a stejně tak Dekorátor. Ten už je zajímavější. Např. ActivityDecorator, LocalizationDecorator, apod, ale jak takovou entitu instanciovat (v konfigu těžko, protože je nutný předávat i ID entity) a stejně si nejsem jistý jestli to je přesně ono (jak by se např. volala metoda isActive() přímo nad entitou ⇒ stejně by se musela implementovat i v ní a v ní volat decorator->isActive() což mi moc nepomůže – jde mi o to nepsat to pořád dokola).
Takže abych to shrnul:
- Extistuje na to nějaký pěkný návrhový vzor?
- Pokud ano, tak jaký?
- A je to vůbec správně? Nebo je už tato úvaha prostě špatná (což si zase až tak nemyslím).
- Any idea jak za použití dekorátoru správně inicializovat entitu?
Díky za reakce.
- Michal Vyšinský
- Člen | 608
Ahoj,
přiznám se, že jsem nečetl celý příspěvek, ale myslím, že pro
začátek by mohla pomoci skvělá přednáška od Martina Štekla v PoSoboty
- castamir
- Člen | 629
Dělám přesně na stejném problému. Pro tuto konkrétní problematiku návrhový vzor neznám, ale použití Trait pro PHP 5.4 to řeší docela slušně. Pro PHP 5.3 žádné lepší řešení než ruční vypisování nemám.
Entity a EntityCollection (resp. EntityList nebo jak jsi to nazval) jsou součástí návrhového vzoru Facade (fasády), ale ten tyhle detaily entit neřeší.
- hAssassin
- Člen | 293
@CherryBoss > uaaa, nova prednaska, mrknu, diky.
@castamir > ono jsem to mozna nazval blbe, resp takto: nejde mi o kolekci vsech entit ale vzdy pouze o jednu jedinou entitu s tim, ze bude umet dohledat sveho predchudce/naslednika, cili bude mit poradi, ale neni to cela kolekce entit. Jako priklad uvedu treba MoznostVAnkete – je to BaseListEntity, jelikoz ma poradi v ramci vsech moznosti v ankete. Nebo PolozkaMenu – je to BaseTreeEntity, jelikoz ma uroven i poradi v ramci urovne. Atd. Takze tady uplne problem nemam, ale co kdybych prave chtel jednu polozku menu, ktera pujde aktivovat a jinou polozku menu ktera aktivovat nepujde (v ramci dvou ruznych entit!)? A idealne tak aby obe dedily primo od BaseTreeEntity a rovnez abych nemusel dopisovat implementaci pro tu aktivaci?
- castamir
- Člen | 629
@hAssassin zaujala mě ta položka menu… co takhle to vzít z druhého konce – mít entitu jen čistě na data (sloupce tabulky 1:1 atributy entity) a tu ostatní logiku posunout o úroveň výš?
- mám nějaký dotaz a získám surová data
- co s nima udělám?
- chci seznam entit? Vytvořím EntityCollection
- chci stromovou strukturu? Vytvořím EntityTree
- chci jinak organizovaná data? Vytvořím EnityWhateverCollection
- naplním strukturu daty (entitami) a dál už pracuju podle potřeby
- hAssassin
- Člen | 293
@castamir > zajímavé, zajímavé. Jen teď teda nevím jestli toto není další vrstva do 5ti vrstvého modelu a nebo to není service vrstva (nebo repository), která by něco takového taky mohla řešit. Pokud by to byla nová vrstva (ikdyž možná je označení vrstva trochu špatné) tak je to velmi zajímavý pohled. Co se mi ale na první pohled moc nelíbí je možnost se stejnou entitou pracovat různě. Jednou může být položka menu jako EntityTree jindy zase jako EntityCollection, což trochu znepřehledňuje a je to takový nekonzistentní, přece jenom pořád pracuju se stejnou třídou.
Nicméně tohle není úplně to na co sem se v tom původním příspěvku
ptal. Problém s typy entit pořád přetrvává, protože pořád můžu mít
entity aktivovatelný/přeložitelný/publikovatelný nebo ne a pořád si přes
dědičnost nevystačím i když ty kolekce přesunu o úroveň výš. Proste
mám nějakou entitu a jí řeknu: ty jsi seznam, lze tě aktivovat a máš
lokalizaci a bum, můžu najednou nad entitou volat
$entity->isActive();
nebo
$entity->getSuccessor();
. Možná jsem do toho ty seznamy a
stromy neměl motat (ostatně je to vlastně totéž jako že je entita
aktivovatelná) :-)
- castamir
- Člen | 629
5 vrstev modelu Facade
- úložiště – databáze, xml atd
- mapper – napojení do db (např. přes dibi), zpracování souboru
- repository / dao – způsob práce s daty, určuje datový celek (tabulka)
- entity a entityCollection – obálka nad řádkem (sloupce) volitelně obalená do kolekce (seznam, strom)
- service – aplikační logika
EDIT: sjednotil jsem entity a entity collection
Editoval castamir (3. 4. 2013 18:41)
- castamir
- Člen | 629
O to už se ti stará mapper. Ale je fakt, že entity collection není
funkčně úplně rovna ostatním vrstvám, neboť ji jde přeskočit a na
úrovni facade můžeš pracovat i s entitami.
Máš pravdu – trochu jsem to domotal… Teď už by to mělo být správně. Nicméně i tak s několika věcmi v článku, na který odkazuješ, nesouhlasím (zejména s ukázkovým příkladem).
Editoval castamir (3. 4. 2013 18:40)
- stekycz
- Člen | 152
Přemýšlím, zda má vůbec smysl mít podobnou/stejnou logiku na jednom místě pro více různých entit. Nejsem totiž přesvědčen, že tato logika bude stejná pro všechny entity – kupříkladu aktivace článku a uživatele se bude skoro určitě nějak lišit :-) Tahle logika by měla být IMHO v service vrstvě.
Pokud ale neřešíš logiku, která nastaví nějaký „flag“, že entita
je aktivní, ale řešíš samotné nastavení toho flagu, pak jsem
jednoznačně proti nějakému spojování logiky do dekorátoru nebo čehokoli
jiného. Jedná se v podstatě o jednořádkový (pro fluent interface
dvouřádkový) setter. Ten lze sadno v každém rozumném IDE vygenerovat nebo
můžeš používat __get
/__set
metody.
- hAssassin
- Člen | 293
@castamir > ano, takto uz s tím, co jsi uvedl, souhlasím. A ano, ani já nesouhlasím s celým článkem, co jsem uváděl, jen ho beru jako základ.
@stekycz > ano, máš pravdu, že aktivace
článku bude jiná než aktivace uživatele, ale to vnitřně neřeším, to je
skutečně starost service vrstvy. Co řeším je ten druhý odstavec, tedy
v případě aktivace/deaktivace pouhé natavení property. Přes
__get
/__set
to řešit nechci a generovat metody
zrovna dvakrát taky ne (nejde ani tak o to generování, ale
o duplicitu kódu).
Jinak, můj problém skutečně asi nejlíp vyřeší Traits
(navíc bude na první pohled jasný, co je implementovaný, např.
ActivityTrait
) a co daná entita umožňuje. Možná, že tenhle
příklad je jednoduchý, ale např. publikace nebo lokalizace, koneckonců
i hrátky se zmiňovaným stromem by takto mohly být řešeny a pokud budou
jednotný, bude to ušetření spousty kódu.
- llook
- Člen | 407
Webové frameworky jsme si všichni už napsali, tak teď si všichni napíšeme vlastní ORM. :-)
Pokud ti chybí vícenásobná dědičnost (traits), tak chceš, aby jedna třída uměla víc věcí. A to je skoro vždycky špatně.
Pořadí položky v seznamu není podle mě vlastností položky, ale seznamu. Může pak existovat služba, která ze seznamu abstrahuje práci s jednou položkou:
$listItem = new ListItem($list, $item);
$listItem->getItem(); // === $item
$listItem->isLast();
$listItem->getNext();
$listItem->getNext()->getNext()->getItem();
$listItem->getPrevious();
$listItem->shiftUp();
//apod.
Nevím, proč se bráníš tomu, aby entity byly pouhými obálkami nad daty. Pokud by entita měla být obálkou nad daty, překladačem textů, publikátorem článku a ještě správcem svého záznamu v nějakém seznamu, tak to už jsou čtyři různé odpovědnosti a k některým bude potřebovat další služby. Pro lokalizaci nejspíš bude entita potřebovat Translator, jak ho získá?
- hAssassin
- Člen | 293
@llook > díky za reakci. Myšlenka je to zajímavá a popřemýšlím o ní.
Ale asi sem v prvním příspěvku moc zabřednul do těch seznamů a stromů, což sem nechtěl. A o to mi ani vlastně nejde. Spíš mi jde o dekompozici entity podobně jako kdysi kdesi naznačoval Filip s formuláři. Proste budu mít entitu, která bude umožňovat aktivaci, uzamčení, nebo i to pořadí, ale současně to je proste jen obálka na data (tomu se nijak nebraním). Všechny tyto věci má normálně uložený jako properties + obsahuje jejich settery/gettery. Jelikož je takových entit ale víc, tak chci nějak vyřešit aby všechny nemusely implementaci setterů/getterů/properties obsahovat, čili aby se neopakoval kód. A přes dědičnost to prostě nepůjde.
- bene
- Člen | 82
@hAssassin: Jeden ze způsobů, jak to vyřešit, je
přes anotace třídy a magickou metodu __get()
, ale já bych se
tomu vyhnul.
I když je psaní/generování setIsPublished($val = true)
,
isPublished()
opruz, není to zase tolik psaní/generování aby to
zastínilo výhody čistého a jasného kódu.
Traits jsou zajímavá myšlenka.
Editoval bene (4. 4. 2013 18:04)
- stekycz
- Člen | 152
Abych pravdu řekl, výsledek použití traitů by podle mě byl ten, že skoro pro každý atribut, který vůbec existuje, budu mít vlastní třídu. Říkám skoro, protože takový trait může obsahovat setteru a getterů více podle účelu. Bojím se ale, že takto bych měl hromadu traitů pro pár doménových objektů. Je to skutečně potřeba?
Připadá mi, že se snažíš nahradit použití interfacu za trait. Když si vezmu, k čemu je cheš používat, jedná se o nějaké vlastnosti. A to, že má objekt nějakou vlastnost, bývá ve většině jazyků vyjádřeno interfacem.
Chápu, že se snažíš zjednodušit si psaní/generování a tím si ušetřit trochu toho kódu, ale myslím si, že v každém kódu se najdou spíše jiná místa, kde by se dal kód ušetřit lépe ;-)