Kdyby\Doctrine use-cases, best practices a jak vám to dává smysl?

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

Ahoj, již nějakou dobu úspěšně používám Kdyby\Doctrine, po čase se mi ale ve větší aplikaci hromadí „nepořádek“ v servicech a to převážně kvůli metodám „findByX()“. Nedávno jsem objevil sílu Query objectů ale zatím jsem nepoužil v praxi, k tomu také směřuje má otázka.

Jestli chápu správně, tak query objecty, by měly kompletně nahradit mé findByXy() metody?

K čemu se tedy v praxi využijí metody rozšířeného repozitáře ?

Zde na fóru se dost často objevuje názvosloví „fasáda“, já v mých aplikacích mám pouze entity a pak nějakou service vrstvu pro úplně každou entitu, ve které řeším prakticky jakoukoliv práci nad tabulkou entity, pro příklad:

<?php

namespace App\Model;

use Nette,
	Kdyby;

final class Examples extends Nette\Object
{
	/** @var Kdyby\Doctrine\EntityManager */
	protected $em;

	/** @var Kdyby\Doctrine\EntityRepository */
	protected $repository;


	public function __construct(Kdyby\Doctrine\EntityManager $em)
	{
		$this->em = $em;
		$this->repository = $em->getRepository(SomeEntity::class);
	}


	public function findByXy($param)
	{
//		...
	}
}

Jedná se v tomto případě o „fasádu“, nebo co to vlastně je? :-)
Také mě zajímá, jestli je to takto správně, nebo je lepší si přes konstruktor předávat přímo repository?

Nemám ještě úplně jasnou představu o struktuře, jaký kód kam umístit aby v tom byl pořádek (nově budu přidávat soubor s query objectem, také prakticky pro každou tabulku databáze).

Několikrát jsem četl 5 vrstev modelu ale zdá se mi to trochu přehnané, proto bych chtěl znát názory zkušenějších uživatelů Doctrine, ideálně i od @FilipProcházka co je teď vlastně in a jak to dává smysl vám?

jiri.pudil
Nette Blogger | 1028
+
+1
-

Ad fasáda: existuje návrhový vzor facade, který zjednodušuje rozhraní nějakého komplexnějšího sledu operací, typicky i nad různými entitami, dalšími službami apod. Tvůj příklad bych tedy za fasádu nepovažoval. A vlastně už taková třída ani není potřeba, protože a) když použiješ query objecty, můžeš pracovat v presenterech a komponentách přímo s EM a repository, b) spousta těch provázaností se dá vyřešit pomocí událostí.

Ad rozšířený repozitář: sílu query objektů vidím v tom, že dokážou schovat i poměrně složitou dotazovací logiku za jednoduché, samopopisné API. Používat QO na triviální dotazy (jako třeba dohledání uživatele podle e-mailu při přihlašování) mi připadá trošku jako jít s kanónem na vrabce, tam je podle mě přímý dotaz přes repozitář zcela dostačující.

Ad struktura: nějakou dobu používám něco takového a zatím vyhovuje:

app
 |- Entities
     |- Gallery
         |- Events
         |   |- NewRankingListener.php
         |- Queries
         |   |- PhotoQuery.php
         |   |- RankingQuery.php
         |- Photo.php
         |- Ranking.php
Filip Procházka
Moderator | 4668
+
+4
-

Né na všechno se ti bude chtít psát hned query object a né na všechno je query object potřeba.

Například findPairs je typický příklad use-casu, který je s QO doslova otravný.

// findPairs automaticky veme primární klíč entity a oindexuje s ním pole
$form->addSelect('country')
    ->setItems($repository->findPairs('name'))

// a pak jen ve zpracování zavoláš
$country = $repository->find($values->country);

Metodu ->find() asi taky hned tak neopustíš, její hlavní výhoda je že jako první sahá do paměti, jestli v identity mapě není entita načtená a vrací tu, kdežto QO položí dotaz do databáze (s QO to v důsledku nesouvisí, může za to způsob jakým funguje DQL).


Fasády vs services je už těžší oříšek. Já většinou aplikace nechávám vyvíjet dost samovolně, hlavně ze začátku, kdy ještě nemám nabouchanou nikde hromadu logiky. Z pravidla si dopředu nechystám „modelové třídy“, když by pak v nich třeba ani nikdy nic nebylo.

  • Začnu tak, že používám findBy([]) a ->find() metody (přímo v presenterech)
  • Když je to pomalé, tak refaktoruju do DQL (přímo v presenterech)
  • Když mám na něco udělat výpis se stránkováním, tak rovnou udělám QO (které pak volám přímo v presenterech)
  • Když už presentery přesáhnou určitou mez, tak refaktoruju do komponent a více QO
  • V momentě kdy se do aplikace začne přidávat nějaká logika (select s pár podmínkama ještě nijak extra složitá logika není), přesouvám dotazování přes QO do fasád.
  • Když mám něco použít na více místech, tak přesouvám do fasád.
  • V momentě kdy je ve fasádě nějaká složitější logika v nějaké metodě, tak z ní refaktoruju ven služby.

Abych to shrnul jak to chápu já

  • Repozitáře a QO čtou data
  • EntityManager ukládá data
  • Fasády jsou „brána“ do modelové vrstvy, kterou používají presentery/api/workery/…
  • Služby jsou jednotky logiky, které provádí nějakou konkrétní operaci a je jedno jestli nad jednou entitou nebo více
  • Fasády sdružují služby do logických celků, které se pak používají z presenterů. Třeba OrderFacade->finishOrder($order), které změní stav objednávky na new, zavolá službu pro rezervaci zboží, vyvolá event na kterém naslouchá poslání emailu a smsky, strhne prachy z uložené karty, …

V podstatě by ses měl snažit dostat do stavu, kdy přes fasády ukládáš (hlavně) i čteš (není zásadní) a v presenteru pak na QO a repository saháš jen výjimečně nebo když jsou stejné podmínky jen na 1 místě. EntityManager je spornej, obecně je lepší flushovat ve fasádách, ale není to boží přikázání a klidně můžeš flushovat i v presenteru pokud to dává smysl.

A pamatuj že nikdy nemusíš mít 1:1 Entity:Fasády, ani Entity:Služby.

Azathoth
Člen | 495
+
0
-

@FilipProcházka když používáš find, findBy i QO objekty přímo v prezenterech, neduplikuje se ti logika, když ji máš v prezenterech a komponentách a buduješ např. API? Kde žádné komponenty nejsou a jsou tam jiné prezentery pro stejné/hodně podobné akce?

Filip Procházka
Moderator | 4668
+
0
-

@Azathoth neduplikují :)

Když mám něco použít na více místech, tak přesouvám do fasád.

Azathoth
Člen | 495
+
0
-

á, to jsem přehlédl. díky.