Pětivrstvý model – facade vs repository

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

Chci používat pětivrstvý model, podobně jako je třeba tady: http://www.zdrojak.cz/…-doctrine-2/ případně u Jana Tichého www.phpguru.cz/…rstev-modelu .

Co jsem vyčetl, tak jej tady používá dost lidí, tak mi snad pomůžete ujasnit pár věcí. Základem chci říct, že píšu repository, ale myslím v podstatě DAO, budu tam i ukládat apod.

Hlavní problém mám s tím, co má být ve fasádě a co v respository. Já osobně chápu fasádu v podstatě jako přeposílače na respository, ale v obou článcích mají metodu publish a dávají ji do fasády, kde nastaví nějaké věci a pak teprve volají repository a tam to uloží. Já bych třeba ten stav nastavoval až v repository a tam ukládal.

  1. mám nějaký objekt, chci jej uložit. Z controlleru dostanu ten daný objekt a nevím jeslti bude insert nebo update, to si zjistím podle toho zda mám nastavené id. Tak a teď otázka, mám zjišťovat zda to id je nastaveno ve fasáde a z repository zavolat už insert či update přímo nebo si to mám vše zjsitit v respoiotry a podle toho se rozhodnout?
  2. Chci mazat záznam z dtb. Potřebuji zjistit, zda vůbec existuje s tímto id, pokud ne vyhodím exception, pokud ano, najdu entitu a tu smažu. Mám řešit to zda záznam existuje ve fasádě, případně i tu samotnou entitu najít a do DAO pro smazání poslat už samotnou entitu, či proste vzít id a probublat ho až do repository a tam to řešit teprve?
  3. Budu si vybírat článek, k tomu článku budu chtít přichytnout komentáře (teď neřešme to, že to za mě udělá ORM samo) a ty komentáře k tomu článku přiřadím. Mám mít ve fasádě dvě repository a tam si to spojit, anebo mám v ArticleRepository zavolat tu CommentRepository z ní dostat ven do fasády už vyplněno.
  4. A ještě kde vlastně má správně vznikat entita? Například dostanu informace z formuláře, v controlleru zpracuji a už tam vytvořím entitu a naplním to tam? Chápu to dobře?

Zkrátka mám v tom trochu guláš, osobně fasádu chápu jenom jako zbavení se volání přímo repository z controlleru, ale zase když z toho uděálm dvou řádkové metody, nevím, jestli má tahle vrstva vůbec nějaký rozumný smysl, krom tedy volání doctrinovského flush()

David Grudl
Nette Core | 8228
+
+3
-

Při takhle nastavené situaci hranice není moc zřejmá. Zkusme to rozšířit. Třeba tak, že při uložení objektu (např. objednávky) se odešle emailová notifikace. Při smazání důležitého záznamu (např. článek) se zazálohuje na disk nebo do jiné tabulky. Apod. Pak je asi zřejmé, že úložiště (ať už ho nazveme jakkoliv) tyto věci řešit nemá. Ale řešit by je neměl ani presenter, ten chceme mít co nejštíhlejší. A najednou začne dávat smysl další mezivrstva (až už ji, opět, nazveme jakkoliv).

Jakékoliv vrstvy abstrakce by měly vznikat z potřeby, nikoliv proto, že je popisuje nějaký článek. Pokud si nejsem jistý, klidně naprasím celou logiku do presenterů. Výhodou je rychle vyřešený úkol (což v praxi je obvykle to jediné skutečně důležité). Zároveň se přitom jasně ukáží slabá místa a rozložení do jednotlivých vrstev se začne samo nabízet. V dalších iteracích pak aplikaci vyčistím do pěkné podoby a zcela přesně vím, proč je rozložená tak, jak je. Zároveň získám zkušenost a příště už můžu podobnou aplikaci rovnou psát čistě.

Filip Procházka
Moderator | 4668
+
0
-

@Jiří Nápravník: Na to je jednoduché pravidlo: nikdy neděď DAO ani Repository, protože jejich práce je persistence, nikoliv tvoje bussines logika.

Jiří Nápravník
Člen | 710
+
0
-

David Grudl: Díky za upřesnění, trochu mi to vnáší světlo do problému. Nicméně v tom příkladu se zálohou důležitého záznamu třeba do jiné tabulky, tam podle mě by to taky mohla řešit ta „repository vrstva“. Samozřejmě záloha na disk, poslání mailu apod. tam je to jasné, že to musí být blíže controlleru než k úložišti.

Filip Procházka: Teď moc nechápu, repository přeci logicyk dědit musím, když tam chci přidat samotné ukládání apod. Ostatně i to tvoje DAO dědí z Doctrinovského respository… Kdybys to nějak rozvedl a ideálně ukázal na příkladu (třeba některý z těch mých) bylo by to ideální.

frosty22
Člen | 373
+
0
-

Ano DAO z Kdyby/Doctrine dědí z repository, ale přidává do něj metody jako je save, fetchPairs, .. v podstatě obecné metody pro každý repository. Dědit za tímto cílem není špatné, ale tady už se nic moc více obecného nevymyslí, pokud nehodláš dělat nějaké vlastní abstrakční features v repositářích.

Ale co se týče konkrétní business logiky jako je vytvoření uživatele, označení objednávky za zaplacené atd. To bys neměl dávat do DAO a ani za tímto cílem vytvářet zděděné DAO.

Jiří Nápravník
Člen | 710
+
0
-

Ok, vidím jasněji:-) Ještě jenom jak je to v případě, když chci něco vybrat !speciálně", zkrátka je třeba pracovat například v Doctrien s QueryBuilderem, tam mi dává větší smysl právě umístění do respository než někam jinam nebo něco opět nechápu?

Jiří Nápravník
Člen | 710
+
0
-

matej21: ano to jsem již četl, nicméně chci se spíše vyhnout použití kdyby/doctrine, je to celkem moloch – jsou v ní další tři závislosti na Kdyby, chci mít model co možná nejméně závislý na několika částech třetí strany, v extrémním případě aby se dal model rozumně přenést i na jiný framework používající Doctrine. Mám rád zkrátka účelové a „stručná“ rozšíření, v tom mi více vyhovuje Doctrine z Nelly. Zajímá mě tak, zda a jak je to řešitelné i jinak než použít Kdyby.

Samozřejmě Kdyby/Doctrine je pěkný ksu práce, to nechci nijak hanit, ale není ideální pro mé použití.

frosty22
Člen | 373
+
0
-

V tomto případě bych to udělal hodně podobně, vesměs takový výtah z Kdyby třeba rozšířit repozitáře o metodu:

<?php
interface IQuery {

	/** @return QueryBuilder */
	public function getQuery(QueryBuilder $qb);

}

// a v repozitáři

public function fetch(IQuery $query)
{
	return $query->getQuery($this)->getResults();
}
?>

Jen schéma asi to funkční nebude, ale více méně takhle by šli taky udělat jednoduché doménové dotazy, ale na druhou stranu, pak zjistíš že ti tam chybí ještě tohle, pak tohle, pak tohle a voalá máš tu nevyladěné Kdyby :=)

Jiří Nápravník
Člen | 710
+
0
-

frosty22 napsal(a):
pak zjistíš že ti tam chybí ještě tohle, pak tohle, pak tohle a voalá máš tu nevyladěné Kdyby :=)

jj, já si taky myslím, že to nakonec tak skončí:-)

Filip Procházka
Moderator | 4668
+
0
-

Jen bych se drobátko ohranil ohledně toho molochu :) On sice kdyby/doctrine je účelně závislý na events a console, ale brzy zjistíš, že bez těchto komponent se doctrine používat nedá a ani to nemá smysl, takže bys je tam doinstaloval sám od sebe.

Ale k tvému problému na začátku.

namespace App;

class Users extends \Nette\Object
{
	private $dao;

	public function __construct(\Kdyby\Doctrine\EntityDao $dao)
	{
		$this->dao = $dao;
	}

	public function save(User $user)
	{
		$this->dao->save($user);
	}

	public function find($userId)
	{
		return $this->dao->find($userId);
	}

	public function findActive()
	{
		$q = $this->dao->createQueryBuilder('u')
			->where('u.active = 1');

		return new \Kdyby\Doctrine\ResultSet($q->getQuery());
	}

	// ...
}

Registrace do configu

services:
	- App\Users(@doctrine.dao(App\User))

Ještě jednou bych zdůraznil, tahle třída co jsem zde načrtl je konkrétní bussines logika, tedy pravidla a konkrétní SQL které dělají tvou aplikaci tvou aplikací. V momentě kdy bys tuto třídu dědil od Repository nebo DAO, tak bys míchal persistenci a bussines logiku, viz SRP.

A ano, máš tam dvě metody navíc, které už jsou v DAO. Ne neměl by sis kvůli tomu vytvářet abstraktního předka, protože je to znásilnení dedičnosti (protože bys dědil abys ušetřil psaní, nikoliv proto, že to dává smysl v tvé doméně).

Jiří Nápravník
Člen | 710
+
0
-

Díky za doplnění Filipe, už mám snad opravdu jasno:)

frosty22
Člen | 373
+
0
-

„protože bys dědil abys ušetřil psaní, nikoliv proto, že to dává smysl“ – z tohoto výroku by šlo docela vycházet, jako taková poučka, zda je využito dědičnosti správně :)

„Dědíš objekt, aby sis ulehčil psaní? Tak to není správně.“ …
jako máme třeba u metod „Pokud při popisu, co dělá tvá metoda použiješ spojku, pak to má dělat metod více“ :)

Jiří Nápravník
Člen | 710
+
0
-

Tak nakonec jsem neodolal dozkoušel Kdyby\Doctrine a DAOs jsou návykové, takže u nich asi zůstanu:-) Nicméně ještě jedna věc. Používae nějaký hydrátor na převod mezi polem a entitou? Já používal Zend\StdLib\Hydrator\ClassMethod. Nicmene Kdyby baseentity ma magicky gettery a settery a tak nema pro mě příliš smysl psát/generovat zbytečně settery/gettery. Nicméně pak logicky ten hydrátor ty property nevidí a nenastaví je. Jak to řešíte?

Filip Procházka
Moderator | 4668
+
0
-

@Jiří Nápravník: nepoužívám, předpokládám že to potřebuješ kvůli formulářům, na to je daleko lepší řešení, jen ho někdo musí udělat :)

enumag
Člen | 2118
+
0
-

Anebo se musí vzít formuláře ze Symfony které validaci provádí rovnou nad výslednou entitou. :)

Jiří Nápravník
Člen | 710
+
0
-

JJ, právě na formuláře. Ok zatím tedy zůstanu u generování getteru/setteru přes IDE.