Architektura aplikace: UI-Facades

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

Ahoj,
poslední dobou sem tam slýchávám o „UI Fasádách“, což by měla být jakási mezivrstva mezi presenterem a modelem (repository). Údajně tento návrhový vzor používá i David na svých školeních.

Co jsem o UI fasádách slyšel:

  • Každý presenter má vždy jednu svou fasádu, a neměl by šahat do fasád jiných presenterů
  • Fasáda v architektuře aplikace zastává funkci aplikační logiky (presentery zobrazovací logiku, modely datovou logiku)
  • Fasáda má přístup k $user (aktuální uživatel), ke všem modelům a k sessions

Když jsem ale zkoušel termín „ui-facades“ googlit, prakticky nic mi to nenašlo. Můžete mě pls nasměrovat na nějaké zdroje, případně o UI Fasádách něco stručně napsat? :-) Díky

P.S. Klasický návrhový vzor Facade znám, zde jde ale podle mně o něco jiného.

arron
Člen | 464
+
0
-

Já teda zkusím odpovědět ačkoliv jsem na Davidově školení nebyl :-)

IMHO (!!) nejde o nic speciálního. Obecně by Controler (Presenter) v MVC (MVP) aplikaci měl drtinou většinu svojí práce někam delegovat. Tzn. měl by se asi starat hlavně o předávání dat, měl by řešit zpracování chybových stavů, ale to je cca všechno. A pak by měl volat výkonné třídy, které teprve zajišťují to, co se má ve skutečnosti reálné provést. To, že drtivá většina PHP programátorů bastlí logiku aplikace do Controlerů (Presenterů) je špatně. Podle mě se David snaží tohleto programátorům sdělit :-) No a ano, ten objekt (podle mě se mu správně říká Business object) je opravdu fasádou :-)

Editoval arron (9. 11. 2012 8:43)

Siam
Člen | 54
+
0
-

Pro mě je dost matoucí to slovo logika. Když si řeknu význam slova logika co znám, tak mě nenapadá žádná spojitost s programováním. Mohl by mi někdo říct co přesně je tou logikou v programování myšleno? Existuje na to nějaká definice?

enumag
Člen | 2118
+
0
-

Zkouším ty fasády použít, ale narážím na pár problémů plynoucích z nepochopení, prosím tedy o rady.

  1. Pokud používám fasády, znamená to, že by Presenter neměl přímo přistupovat k modelům (objekty typu Repository) když potřebuje data, ale vždy si o ně říct fasádě?
  2. Je ve fasádách přípustné volání metod jako „related“ z NDB nebo by na to Entity měla mít konkrétní metodu (používám něco jako Fabik\Database).
  3. Je špatně pokud fasáda implementuje \Nette\Security\IAuthenticator?

Editoval enumag (4. 2. 2013 18:34)

srigi
Nette Blogger | 558
+
0
-

Podla mna sa jedna o tu poslednu vrstvu v 5 vrstvovom modeli, ktory tak slavne popisal Jan Tichy.

castamir
Člen | 629
+
0
-

@enumag IMHO

  1. o data žádáš fasádu
  2. related přeskakuje fasádu a jde přímo do modelu / entity, ale hlavně je vázán na konkrétní mapper, takže related bych v něm nepoužíval a volal si patřičné metody dolů
  3. je nějaký důvod, proč ji vázat na authenticator?
enumag
Člen | 2118
+
0
-

@srigi: Díky za odkaz, tenhle článek jsem sice kdysi viděl, ale bohužel jsem jej tehdy nepřečetl celý. :-)

@castamir: Díky za názor, shoduje se s tím mým. :-) Na authenticator se ptám protože si nejsem jistý kam bych měl dát metodu na šifrování hesel, jestli do authenticatoru nebo jinam (kam?). Jedna fasáda tu metodu potřebuje taky protože ukládá uživatele. Není mi jasné zda by authenticator měl být úplně samostatná třída nebo zda je vhodné toto rozhraní implementovat nějakým Repository / Facade / Whatever které dělá i další věci s autentizací nesouvisející.

Pro ty kdo toto téma budou číst později: přečíst tenhle článek asi nebude od věci i pokud nepoužíváte Doktrínu (můj případ).

Editoval enumag (4. 2. 2013 20:02)

castamir
Člen | 629
+
0
-

Na authenticator se ptám protože si nejsem jistý kam bych měl dát metodu na šifrování hesel, jestli do authenticatoru nebo jinam (kam?). Jedna fasáda tu metodu potřebuje taky protože ukládá uživatele…

@enumag To je přesně ten důvod, proč na to mít vlastní třídu/službu, kterou si ve fasádě zavoláš.

enumag
Člen | 2118
+
0
-

@castamir: Na authenticator nebo na tu šifrovací fci? Měla by ta funkce být v authenticatoru nebo jinde?

castamir
Člen | 629
+
0
-

@enumag prostě mít authenticator s metodou na šifrování. Ve fasádě vytvořit instanci authenticatoru (popř. zavolat službu) a pak z něj volat kontrolu hesla…

castamir
Člen | 629
+
0
-

Ještě mě tak napadá k tomu related… v podstatě bys neměl dostat objekt ActiveRow, ale nějakou entitu, kterou ti vrátí Repository. ActiveRow se použije max v mapperu, který nahoru pošle jen surová data

enumag
Člen | 2118
+
+1
-

@castamir: Opět mi potvrzuješ co jsem si myslel – a přesně to jsem od tebe potřeboval. ;-) Díky!

Ještě jedna věc. Mám formulář a mám model kam je chci uložit. Struktura dat ve formuláři ale moc neodpovídá struktuře dat v databázi takže nestačí jen předat modelu $form->getValues(). Moje otázka zní, kde by měla být konverzní logika z dat co vrátí formulář na data pro model?

  1. V Presenteru? Ten by asi neměl moc znát strukturu databáze, vždyť ani nemá přímý přístup k modelu.
  2. Ve třídě Facade? Tuto třídu vnímám jako vrstvu modelu, takže by asi neměla pracovat s komponentami ani vědět jak vypadá konkrétní formulář.
  3. Každý formulář jako specializovanou třídu která bude řešit i ukládání? Co když je pro konkrétní formulář ukládání různé (jeden formulář který slouží pro vytváření i editaci)? Společné věci v abstraktním předkovi a každý specializovaný příklad (AddUserForm, EditUserForm) samostatně?
  4. Jinde?

Editoval enumag (4. 2. 2013 22:40)

Filip Procházka
Moderator | 4668
+
0
-

Já to mám s Doctrine velice jednoduché. Protože nejsem omezen relacemi tabulek, ale mám entity, tak si můžu formulář poskládat (pomocí $form->addContainer()) tak, že jeho datová struktura odpovídá 1:1 té v entitě (asi by to šlo i s tabulkama, ale z nějakého důvodu mi to moc nevoní).

Pokud máš formulář o pár políčkách (typicky přihlášení, změna hesla), tak tě tohle vůbec netrápí, ale u větších formulářů je to neskutečně pohodlné.

castamir
Člen | 629
+
0
-

@enumag Opět. Presenter je „controler“, který umí spravovat komponenty. Mimo jiné i zpracovává signály včetně signálů od jeho komponent. Takže stačí v tomto, pro formulář, callbacku vzít údaje z formuláře (tedy pole) a zavolat si z fasády metodu pro uložení dat. Fasáda buď může sama rozhodnout, zda má vložit či aktualizovat záznam, nebo jí to lze vnutit a toto rozhodování nechat na výše zmíněném callbacku. Obě řešení jsou z mého pohledu korektní.

Fasáda by, aspoň podle mě, měla být schopna třídit data – nepotřebné hodnoty zahazovat atp..

enumag
Člen | 2118
+
0
-

Odpovím si sám, zase jednou jsem vymyslel kolo. :-D (thx @Filip Procházka za diskusi na jabberu)

nanuqcz
Člen | 822
+
0
-

Tak jsem zase přemýšlel nad „fasádama“ a není mi jasná jedna věc. Jakým způsobem by měla fasáda správně komunikovat s presenterem?

Příklad: Dejme tomu, že máme akci pro hromadný import osob, třeba z nějakého dlouhého textu, který musí fasáda rozparsovat a osoby uložit do DB.

1) Komunikace návratovou hodnotou

public function importFormSubmitted($form) {
	$result = $this->facade->import($form->values->persons);  // return TRUE|FALSE

	if ($result) {
		$this->flashMessage('Import OK');
		$this->redirect('this');
	} else {
		$this->flashMessahe('Import error');
	}
}

Toto se mi zdá na první pohled jako jednoduché a hezké řešení.

2) Komunikace vyjímkami

public function importFormSubmitted($form) {
	try {
		$this->facade->import($form->values->persons);
		$this->flashMessage('Import OK');
		$this->redirect('this');
	catch(PersonFacadeBadFormatException $e) {
		$this->flashMessahe('Import error');
	}
}

Tento způsob mi určitě při prvním čtení příspěvků chcete pochválit (ale dočtěte nejdřív na konec :-))

3) Komunikace návratovým polem s informacemi

public function importFormSubmitted($form) {
	$info = $this->facade->import($form->values->persons);

	$this->flashMessage($info['message'], $info['status']);
	$this->redirect('this');
}

Z tohoto způsobu mám prostě nějaký špatný pocit. Zdá se mi, že toto není ideální řešení, i když jeho zápis je nejkratší.

4) Komunikace referenčním parametrem s informacemi

public function importFormSubmitted($form) {
	$this->facade->import($form->values->persons, $info);  // informace o výsledku jsou předány do proměnné $info

	$this->flashMessage($info['message'], $info['status']);
	$this->redirect('this');
}

Viz možnost č. 3.


Takže pravděpodobně vyhrávají možnosti 1 a 2. V praxi je ale problém vždy složitější. Chtěl bych například v šabloně vypsat, které osoby se nepodařilo importovat (např už jsou v databázi, nebo mají špatný formát rodného čísla) – přičemž zbytek osob chci aby se importovali vpořádku. A najednou nám způsoby 1 a 2 nestačí, a musíme šáhnout po škaredém 3 či 4.

Jaký je tedy best-practise pro předávání zpráv Facade -> Presenter?

enumag
Člen | 2118
+
0
-

@nanuqcz: Osobně bych použil možnost 1 s tím, že ten foreach by byl v presenteru:

public function importFormSubmitted($form) {
    foreach ($form->values->persons as $person) {
        $result = $this->facade->import($person); //bool
        //flashMSG že $person nešla uložit či jiný způsob hlášení chyby
    }

    //během toho foreach je nutná bool proměnná zda nastala nějaká chyba nebo ne
    if (...) {
        $this->flashMessage('Import OK');
        $this->redirect('this');
    } else {
        $this->flashMessahe('Import error');
    }
}

Dalo by se použít i 2, ale tam by ten try-catch blok musel být uvnitř toho foreach, což používám jen velmi nerad. Proto raději takto.

Editoval enumag (26. 3. 2013 8:05)

mkoubik
Člen | 728
+
0
-

Ještě se dá použít:

$presenter = $this;
$this->facade->onImportSuccess[] = function($status, $message) use ($presenter) {
    $presenter->flashMessage($message, $status);
    $presenter->redirect('this');
}

...

public function importFormSubmitted($form) {
    $this->facade->import($form->values->persons);
}

i když to na první pohled nevypadá tak elegantně.

Výhoda je, že tam můžeš zaregistrovat i další callbacky, nejen presenter (odeslání mailu, uložení do logu, …). A asi by se použily obecnější parametry než $status a $message – ty jsou šité na míru právě presenteru.


Edit: Aha, tady se bavíme o UI fasádách, tam bude ten callback téměř vždy jen jeden. Tohle se vyplatí spíš u klasických services.

Editoval mkoubik (26. 3. 2013 8:55)

nanuqcz
Člen | 822
+
0
-

enumag napsal(a):

public function importFormSubmitted($form) {
    foreach ($form->values->persons as $person) {
        $result = $this->facade->import($person); //bool
        //flashMSG že $person nešla uložit či jiný způsob hlášení chyby
    }

Myslel jsem to tak, že ve $form->values->persons je dlouhý textový řetězec (třeba ve formátu csv), který teprv musí fasáda rozparsovat :-)

bazo
Člen | 620
+
0
-

ja nevidim nic zle na sposobe 3. ked komunikujes s nejakym api tiez dostavas ako odpoved nejake pole popripade objekt. ak sa ti nepaci pole mozes vytvorit objekt Response, ktory moze obsahovat napr status, zoznam nepodarenych importov atd

voda
Člen | 561
+
0
-

Já používám kombinaci 2 a 3. Výjimka se vyhodí třeba pokud je vstup ve špatném formátu. Místo pole pak vracím result object, kde můžou být konkrétní informace, že se např. nepodařilo naparsovat řádek xy. Samozřejmě pak záleží na tom jestli se musí zpracovat celý vstup nebo jestli se můžou některé řádky v případě chyby přeskočit.

newPOPE
Člen | 648
+
0
-

No v pripade ako pise @nanuqcz, ze chce vypisat zaznamy ktore neslo importovat by bol dobry este medzikrok ktory data overi.

Nepride mi ako dobry napad urobit to ako pise. Vrazim tam nieco, vsak sa nieco stane a potom budem lustit ktore osoby mam este dodat :-). Pozor ale tie ktore uz tam su musim zmazat!

Siel by som cestou:

  1. Upload csv (.xls…)
  2. Check dat a vypis toho co je zle (user si to moze upravit priamo v editore)
  3. Ak check prechadza tak data (mozem ich mat niekde ulozene ak check presiel) importnem.

Co sa tyka tych 4 postupov. Osobne som za vynimky. Nie som si isty ale mozno by slo tie data ktore suvisia s tou vynimkou narvat ako 3. parameter danej vynimky ;-). Tym padom sa data suvisiace s vynimkou prenesu tam kam ich „potrebujes“.

Vynimky aj z dovodu, ze ked zacnes pouzivat Eventy (emaily, logy, …) tak iny sposob ani nepripada (samozrejme neviem vsetko :-)) do uvahy.

castamir
Člen | 629
+
0
-

O mazání se starat nemusíš, od toho je tu rollback. 3. a 4. zachytávání můžeš vyřešit přes výjimky přesně jak to sepsal @newPOPE.

bene
Člen | 82
+
0
-

Vyjímky bych použil v případě, že se nenaimportuje nic, prostě došlo k chybě.
Pokud potřebuješ vrátit log o neimportovancýh uživatelech, přikláněl bych se k struturovanému poli, lépe však k Resul objektu. Logicky, obyč true/false ti nestačí a možnost 4 odporuje logickému chápání vstup/výstup.