Návrhový vzor pro entity při 5ti vrstvém modelu

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

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:

  1. Extistuje na to nějaký pěkný návrhový vzor?
  2. Pokud ano, tak jaký?
  3. A je to vůbec správně? Nebo je už tato úvaha prostě špatná (což si zase až tak nemyslím).
  4. Any idea jak za použití dekorátoru správně inicializovat entitu?

Díky za reakce.

Michal Vyšinský
Člen | 608
+
0
-

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

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

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

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

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

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)

hAssassin
Člen | 293
+
0
-

aha, ja mel vzdy za to ze tam entityCollection neni ale naopak tam je primo uloziste (DB, FS, whatever)… Viz PHP Guru. V tom je mozna ta chyba…

Editoval hAssassin (3. 4. 2013 18:11)

castamir
Člen | 629
+
0
-

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

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

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

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

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

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

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