tiny ‘n’ smart
database layer

Odkazy: dibi | API reference

Oznámení

Omlouváme se, provoz fóra byl ukončen

Lean Mapper, DI, formuláře – vzorový projekt

před 6 lety

Šaman
Člen | 2275

Ahoj, jak jsem slíbil, zveřejňuji svůj ukázkový projekt s využitím LM. Stahujte jen otagované verze, ty jsou spustitelné a relativně stabilní.

Je to obligátní Todolist z Quickstartu, i když došlo k drobým změnám v ER diagramu a tedy i v databázi. LM je zatím použit ve velmi jednoduché podobě (žádné filtry, enumy, apod.), další rozšiřování bude probíhat postupně.
Všechny závislosti jsou řešené pomocí DI, coding standard je podobný Nette, snažím se vytvořit jakýsi best practise pro jednoduché Nette aplikace.

Do tohoto vlákna pište prosím jak dotazy, tak hlavně připomínky, nejen k LM, ale i k celkové architektuře aplikace, od namespaců po configy.

před 6 lety

Šaman
Člen | 2275

Pár poznámek k akruální verzi 0.52:

  • Jediný uživatel je ‚admin‘ a heslo libovolné (kontrola je zatím vypnutá). Časem se budou dát vytvářet další.
  • Authenticator je první na řade s refaktoringem (bude závislý na UserRepository, nikoliv přímo na Connection).
  • Jediný formulář vytvářený jako komponenta pomocí továrničky je LoginForm (ostatní jsou zatím postaru, jednodušším způsobem).
  • Držím konvenci, že getXxx() vrací entitu, nebo NULL, findXxx() vrací kolekci.
  • Při použití magických getByFoo() a findByFooAndBar() je velmi doporučeno vyjmenovat použité metody v anotacích jednotlivých repository. Zajistíte si tak napovídání a odmagičtíte tyto metody.

před 6 lety

Jan Suchánek
Backer | 403

@Šaman: pěkné hned du vyzkoušet, jen drobnost nebylo by lepší mezi UserRepository dát ještě UserManager?

před 6 lety

Casper
Člen | 253

Do tohoto vlákna pište prosím jak dotazy, tak hlavně připomínky, nejen k LM, ale i k celkové architektuře aplikace, od namespaců po configy.

Když vidím, že tam nemáš žádné fasády, rád bych věděl, kde bude později posílání emailů, transakce a vůbec složitější use-casy, kde ukládáš více entit? Tedy například kam přijde metoda registerUser()?

před 6 lety

Šaman
Člen | 2275

@Casper: Zatím je to poměrně prázdné a fasády mi připadají zbytečné. Nechci, aby to vypadalo zbytečně složité. Na druhou stranu pokud by se aplikace rozrostla a v repozitářích by začaly vznikat složitější metody, tak samozřejmě fasády přidám. Nebo ty bys je tvořil hned v základu?

@jenicek: Čemu říkáš manager? Té fasádě? Ta by byla nad repozitory.

Snažím se o implementaci návrhového vzoru DAO a Repository, i když spojené dohromady a pojmenované trochu nepřesně Repository. Celkově v aplikaci máme spodní 4 vrstvy pětivrstvého modelu, servisní vrstva/fasáda je podle mě už spíš záležitost aplikační logiky, než ORM. Takže dokud aplikace nic neumí, tak nemohu říct jaké fasády budu potřebovat.

Editoval Šaman (25. 10. 2013 15:11)

před 6 lety

Jan Suchánek
Backer | 403

@Šaman: ok, manager = fasáda.

Editoval jenicek (25. 10. 2013 12:01)

před 6 lety

Casper
Člen | 253

@Šaman:
No já když jsem s Nette začínal, tak jsem se řídil quickstarty apod, ale všude se používala nějaká zjednodušená architektura (protože prostě pro quickstart víc abstrakce nepotřebuješ), nicméně jsem na to pak trochu doplácel. Takže ano, kdybych já dělal nějaký ukázkový projekt, použiju ty fasády rovnou.

Celkově v aplikaci máme spodní 4 vrstvy pětivrstvého modelu, servisní vrstva/fasáda je podle mě už spíš záležitost aplikační logiky, než ORM. Takže dokud aplikace nic neumí, tak nemohu říct jaké fasády budu potřebovat.

Máš pravdu, že fasáda je aplikační logika, nicméně i primitivní aplikace nějakou má. Sice tam budeš mít pravděpodobně jen převolání nějaké metody Repository, nicméně už tam tu abstrakci máš a aplikace je připravená na snadné rozšíření. Což se rozhodně stane, pokud z ní někdo bude vycházet ve svém projektu. A pak se ti nestane, že začátečníci budou rvát aplikační logiku do Repository/DAO, kam rozhodně nepatří (a tím vytvářet například takové zrůdnosti jako závislosti mezi Repository).

Editoval Casper (25. 10. 2013 12:04)

před 6 lety

Šaman
Člen | 2275

@Casper: Máš pravdu, došlo mi to během výuky. (Je zajímavý, kolik věcí si člověk ujasní, když se je snaží vysvětlit někomu jinému). Jako první fasádu bych už měl vytvořit TaskService a hodit do ní metodu setDone().
Protože DAO/Repository (i když se definice různí) dohromady mají řešit jen základní CRUD operace. Naopak presenter (nebo komponenty) by neměly pomocí Repository provádět složitější operace, než čtení s nastavením nějakého řazení/limitů. Takže už metoda setDone() musí být někde mezi Repository a aplikační vrstvou.
Zjednodušeně se dá říct, že aplikace (presentery, komponenty) může pomocí čistých Repository získávat data, ale ukládat by je měla pomocí servisní třídy.

Otázka zní, jak bychom těmto třídám servisní vrstvy/fasády měli říkat:
TaskService, TaskFacade, TaskManager, TaskModel,…? Můj favorit je TaskService. Protože u složitějších aplikací může být takováto třída i k jedné konkrétní činnosti nad více repozitáři (např. PublishService).

před 6 lety

Šaman
Člen | 2275

Tak jsem na poslední chvíli ještě stihl aktualizaci – refaktoring Authenticatoru (zrušení závislosti na Connection), s tím souvisí ukázka virtuální metody v anotaci UserRepository.
A druhá věc je zavedení servisní třídy TaskService. Aspoň jako první nástřel.

Teď jedu na víkend, vrátím se v pondělí. LM prý bude na poslední sobotě, tak doufám, že bude nějaký záznam přednášky.

před 6 lety

Casper
Člen | 253

@Šaman:

Zjednodušeně se dá říct, že aplikace (presentery, komponenty) může pomocí čistých Repository získávat data, ale ukládat by je měla pomocí servisní třídy.

Máš pravdu, nicméně osobně kvůli jednotnému přístupu používám všude v aplikaci fasády. K Repository/DAO přistupuji jen z fasád. Nelíbilo se mi totiž, že by aplikace měla přístup k tak obecné metodě jako je persist. Ukládání entit vyžaduje většinou dost práce okolo (navázání jiných entit, transakce, emailing, …) a to by rozhodně neměl dělat nějaký presenter. Samozřejmě můžeš mít konvenci, že nic takového dělat nebudeš, ale preferuji trochu defenzivnější přístup :)

Otázka zní, jak bychom těmto třídám servisní vrstvy/fasády měli říkat.

Osobně mám vše pojmenované *Facade, nicméně vše kromě *Model mi přijde použitelné.


Možná bych spíše zvážil co vyžadovat ve fasádních metodách za parametry. Zda entity, či jejich id. Osobně používám Entity, abych měl jistotu typové kontroly a nemohl nechtěně předat například id jiné entity.

A ještě jsem si u různých komponent (např. CatalogControl) všiml, že na jejich vytvoření nepoužíváš generované továrničky i přesto, že ti to běží na Nette 2.1. Máš k tomu nějaký důvod? Vždyť takhle musíš zbytečně vyžadovat cizí závislosti v presenteru jen proto, abys je předal této komponentě. Tohle za tebe může vyřešit DIC.

A ještě taková technická. Měl jsem za to, že při použití composeru se složka s knihovnami necommituje. Máš nějaký důvod, proč máš na githubu i složku libs? … a vůbec, jak se ti podařilo přes composer nainstalovat dev nette bez testů apod?

před 6 lety

tsusanka
Člen | 23

Měl bych ještě dotaz na Collection. Např. v UserRepository máš getByUsername($username), předpokládejme, že tam je ještě findByFirstName a vracelo by to kolekci, protože jméno není unikátní. Jak bys nyní udělal pokud bys tento výsledek chtěl seřadit? To by asi ideálně mělo být možné na Collection, ne? Ale ta už má onen výsledek, takže tam se k fluentu nijak nedostanu. Pletu se? Díky

před 6 lety

Šaman
Člen | 2275

tsusanka: Collection vychází z nativní LM Entity getCollection(), která jako parametr obdrží čisté pole dat a já jen místo pole vytvářím ArayObject. Případné řazení a limity je tedy nutné řešit ručně a pokud chceš řadit už na úrovni databáze, tak musíš v mapperu. Na ‚objektové straně‘ ORM už by nemělo být žádné DibiFluent, repository ta data mohla čistě teoreticky získat třeba přes MemcacheMapper a tedy bez nějakého dibi datasource.

Casper:

  1. Fasády – zkusím to, ale pak bude potřeba přidat nějakou podporu anotací, která by virtuální metody zapsané v anotaci delegovala na Repository. Zastávám názor, že cokoliv nemůž být napovídáno, to je špatně zdokumentováno, nebo příliš magické. A psát „trubičky“ – metody které jen předají informace repozitáři, to se mi nechce. Jinak mnohdy je zbytečné vytvářet Entity jen pro navázání na jinou entitu, proto akceptuji jak Entitu, tak její id. (Např. při vytvoření nového katalogu ho musím přiřadit nějakému uživateli. Id máme jednoduše v identitě, ale na vytvoření entoty User bych potřeboval i UserRepository, nebo UserService).
  2. Generované továrničky – máš pravdu, jsou lepší, jen jsem zatím nestihl převést všechny komponenty (ukázkou je LoginForm), bude co nejdříve.
  3. Nepoužívám Composer, právě třeba proto, že Nette mi to stahuje i se spoustou balastu. Navíc z minula mám zkušenosti s tím, že aplikace odladěná na jedné verzi nemusí šlapat v nové verzi. A 2.1 je stále dev a mění se. Takže v repozitáři je všechno, kromě souboru config.local.neon, který je v gitignore a je potřeba ho vytvořit zkopírováním a přejmenováním souboru config.local.neon.example. A pak ho samozřejmě upravit podle sebe.

před 6 lety

Jan Tvrdík
Nette guru | 2550

Šaman wrote: Nepoužívám Composer, právě třeba proto, že Nette mi to stahuje i se spoustou balastu.

Musíš to instalovat s přepínačem --prefer-dist. Pak se např. nestahují testy. Viz .gitattributes.

před 6 lety

tsusanka
Člen | 23

@Šaman myslíš, že je tedy okej to řadit (limitovat) až v rámci Collection phpkem? To při větším oběmu dat přeci smrdí, ne? Jinak v repu bych to musel napsat celý znovu, např:

public function findByPrice($price)
{
    $data = $this->connection->select('*')
        ->from($this->getTable())
        ->where('[price] = %i', $price)
        ->orderBy('[price] DESC')
        ->fetchAll();
}

a nemohu zde použít magické findByPrice a pak až seřadit. Říkám to dobře?
(sory za nechápavost, jen si to chci ujasnit..)

před 6 lety

Šaman
Člen | 2275

tsusanka:

  1. LM vytvoří array, metoda getCollection ho jenom obalí objektem. V současné verzi LM není možné vracet Datasource (Dibi fluent). To nesouvisí s moji ukázkovou aplikací, ale je to jedna z vlastností použitého ORM.

Datasource je totiž stále dost blízko databázi, v datasource např. není možné řadit podle vypočítané property (které neodpovídá žádný sloupec v DB), případně řadit podle číselníků (třeba abecední řazení podle typu, pokud je ve sloupci typ uloženo jen jeho id). Tohle ORM nám vrací pole hotových objektů, zatímco DibiFluent je jen připravený SQL dotaz. Proto již není možné výsledek na objektové straně nějak filtrovat pomocí SQL.
Je to jeden z důvodů, proč jsem vytvořil třídu Collection – chci do ní přidat i metody pro řazení, aby se to nemuselo dělat na úrovni presenteru pomocí procedurálních metod pro řazení polí.
Vzhledem k tomu, že nejsem autorem LM, tak tento bod bez trochu s rezervou. Neznám všechny možnosti LeanMapperu.

  1. Problém s nepoužitelností magických get a find metod pro další modifikaci ještě v repository je dobrá připomínka a zkusím se nad tím ještě zamyslet. Myslím, že uvnitř modelu (aspoň uvnitř repository) by se dotazy skládat mohly.

Editoval Šaman (28. 10. 2013 13:32)

před 6 lety

duke
Člen | 637

@Šaman Obávám se, že tsusanka má pravdu. V situaci kdy máš v tabulce větší počet záznamů, musíš prostě řadit záznamy na úrovni SQL. Nechat si vytvořit tisíce objektů, abys je pak mohl setřídit v nějakém speciálním třídícím Collection objektu, za účelem toho, abys mohl např. 10 z nich zobrazit, je cesta do pekel. Rozhodně bych takováto řešení do vzorového projektu nedával. Spíše bych se přimlouval za nějaký jednoduchý query objekt, který taková třídění a stránkování na úrovni SQL umožní, a ukáže tak začátečníkům s LM, jak problém vhodně řešit.

před 6 lety

Šaman
Člen | 2275

@duke: Obávám se, že si nerozumíme.

  1. ORM vrací objekty, nikoliv datasource. To je jedna z jeho klíčových vlastností pravých ORM a s tím se musí pracovat. Má to už v názvu – objektově relační mapper.
  2. Datasource (často používaný třeba v různých datagridech) je často query builder, tedy dělá podobnou činnost jako ORM, ale s jinou filosofií.
  3. Nad ORM můžeme pracovat s property jednotlivých entit (třeba řadit podle vypočtené hodnotou, která v db není – zkuste si jinak vyřešit problém vyhledávání a řazení obdélníků podle obsahu, když v databázi máte jen rozměry stran). Nad datasourcem naopak můžeme efektivně modifikovat přímo SQL dotaz – datasource má daleko blíže k databázi. Takže je často vhodnější pro různé tabulkové výpisy a stránkování.
  4. Každý přístup má tedy určité nevýhody a samozřejmě existují způsoby, jak se s nimi vypořádat. Nevýhoda datasourců se dá řešit joiny, nebo výpočty na úrovni databáze (čímž se nám ale trochu rozplizne aplikační logika mezi PHP a databázi a navíc se snižuje přenositelnost mezi různými db enginy). Nevýhoda ORMů se dá řešit tak, že v repository (nebo jiné ORM vrstvě) máme dotazy pro náš konkrétní dotaz. Takže např. $userRepository->findByName($name, $order = 'ASC', $limit = NULL);. To, že to ukázkový příklad zatím neumí, je jiná věc a podle této diskuze to asi má prioritu.

⇒ Rozhodně nebudeme hledat a hlavně VRACET spoustu zbytečných záznamů, to by byla školácká chyba. Jen Collection není datasource, takže již není možné ovlivňovat dotaz do db (to musíme udělat dříve) a řadící funkce jsou jen pomocné. Třeba pokud chceme všechny studenty nějaké třídy (v dotazu nebudeme ani řadit, ani limitovat), tak je před výpisem můžeme seřadit. A nebo seřadíme všechny vrácené obdélníky podle obsahu, protože již máme objekty, které příslušnou propery mají.

Koukal jsem na nějaké datagridy a třeba DoctrineDataSource jen využívá její nízkoúrovňový přístup k databázi. V tom případě my můžeme použít na běžnou práci LeanMapper a třeba pro NiftyGrid použijeme DibiFluentDataSource (ale pozor, je to trochu obejití LM a proto se nemůžeme spolehnout na pravidla, která jsou definovaná v LM entitách (můžeme třeba vložit do db neexistující hodnotu výčtu)).

Je to teď trochu srozumitelnější?

před 6 lety

Jan Suchánek
Backer | 403

@duke: Tzn.: nepoužívat/používat Query object jak o něm mluví Tharos na PS a píše tady?

před 6 lety

Šaman
Člen | 2275

Tak jsem viděl přednášku z #PoSoboty a pár věcí časem nejspíš předělám. QueryObject pravděpodobně slouží k tomu, na co se ptal @tsusanka.
Nicméně do teď jsem o QueryObjectu neslyšel a základní práce se obejde bez něho.
Složitější záležitosti, třeba komunikace s DataGridem pomocí LeanMapperu (tedy bez obcházení jak jsem psal výše) budou muset být nejprve zdokumentovány od Tharose, nebo si je musím vyzkoušet.

před 6 lety

Jan Suchánek
Backer | 403

@Šaman: Gridu předat jen co potřebuje Connection nebo DataSource. Casper o tom píše tady.

Jen nevím jak případnou službu pojmenovat (možná \Datagrid\Repository). Slušně v tom plavu.

Beru to tak, že s Gridem se netrápit, popřípadě si vyrobit vlastní, ale na ostatní používat jen LM.

Casper napsal(a):

@jenicek:

Nevidím moc důvod proč aplikaci zprostředkovávat DibiRows či dokonce celý Fluent. Od toho chceš používat ORM, aby jsi pracoval čistě s Entitami ne? Pokud chceš používat ORM, napiš si model tak, aby ti vracel jen entity a v aplikaci pak pracuj jen s nimi. Výjimkami snad můžou být jen Datagridy, které Fluent vyžadují jako nějaké své DataSource. Nicméně těm už můžeš injectnout celé DibiConnection, to s ORM (a Repository) moc nesouvisí (případně nechť mě zkušenější opraví jak to řeší oni).

před 6 lety

duke
Člen | 637

Šaman napsal:

@duke: Obávám se, že si nerozumíme.

  1. ORM vrací objekty, nikoliv datasource. To je jedna z jeho klíčových vlastností pravých ORM a s tím se musí pracovat. Má to už v názvu – objektově relační mapper.
  2. Datasource (často používaný třeba v různých datagridech) je často query builder, tedy dělá podobnou činnost jako ORM, ale s jinou filosofií.

V těchto bodech si naprosto rozumíme. Nikdy jsem nepředpokládal ani netvrdil opak.

  1. Nad ORM můžeme pracovat s property jednotlivých entit (třeba řadit podle vypočtené hodnotou, která v db není – zkuste si jinak vyřešit problém vyhledávání a řazení obdélníků podle obsahu, když v databázi máte jen rozměry stran). Nad datasourcem naopak můžeme efektivně modifikovat přímo SQL dotaz – datasource má daleko blíže k databázi. Takže je často vhodnější pro různé tabulkové výpisy a stránkování.

Toto je pravda pouze ve speciálních případech, kdy v php skriptu pracujeme se všemi záznamy z tabulky (tj. veškeré řazení a stránkování pak místo v DB děláme v PHP). Pokud je ale záznamů hodně, bez databázového řazení a stránkování se neobejdeme, chceme-li aby aplikace pracovala efektivně. Navíc databázové řazení lze použít i v případě menšího počtu záznamů (a i zde nejspíš efektivněji než v PHP), takže řazení na úrovni ORM je něco, co považuji za nadbytečné. Co se týče zmíněného příkladu řazení obdélníků podle obsahu, platí, že pokud předem vím, že budu potřebovat řadit tímto způsobem, dává smysl mít obsah těchto obdélníku uložený v databázi a mít také nad příslušným sloupcem vytvořený index. Řadit to až v PHP může být praktické pouze pokud mám v takové tabulce jen malé množství obdélníků.

  1. Každý přístup má tedy určité nevýhody a samozřejmě existují způsoby, jak se s nimi vypořádat. Nevýhoda datasourců se dá řešit joiny, nebo výpočty na úrovni databáze (čímž se nám ale trochu rozplizne aplikační logika mezi PHP a databázi a navíc se snižuje přenositelnost mezi různými db enginy). Nevýhoda ORMů se dá řešit tak, že v repository (nebo jiné ORM vrstvě) máme dotazy pro náš konkrétní dotaz. Takže např. $userRepository->findByName($name, $order = 'ASC', $limit = NULL);. To, že to ukázkový příklad zatím neumí, je jiná věc a podle této diskuze to asi má prioritu.

To, že si uložím předvypočtené obsahy obdélníků do databáze nepovažuji za rozpliznutí aplikační logiky. Prostě si ukládám pomocná data, která využiji v budoucnu (pro rychlejší řazení). Možná ale mluvíš o něčem jiném, alespoň ten argument se snížením přenositelnosti tomu nasvědčuje, neboť já tu nic takového nevidím.

⇒ Rozhodně nebudeme hledat a hlavně VRACET spoustu zbytečných záznamů, to by byla školácká chyba. Jen Collection není datasource, takže již není možné ovlivňovat dotaz do db (to musíme udělat dříve) a řadící funkce jsou jen pomocné. Třeba pokud chceme všechny studenty nějaké třídy (v dotazu nebudeme ani řadit, ani limitovat), tak je před výpisem můžeme seřadit. A nebo seřadíme všechny vrácené obdélníky podle obsahu, protože již máme objekty, které příslušnou propery mají.

Ten pomocný charakter řadících funkcí mi pořád nějak není jasný. Pokud tedy nemluvíš jen o tom speciálním případu, kdy mám jen několik málo záznamů v tabulce a řadím je podle vypočítané property, kterou se mi nechtělo ukládat do databáze. A to mi přijde natolik specifické, že bych to do vzorové aplikace nedával. Spíše to nováčky svede z cesty a budou tím chtít řešit i situace s větším počtem záznamů…

jenicek napsal:

@duke: Tzn.: nepoužívat/používat Query object jak o něm mluví Tharos na PS a píše tady?

Nevím, co říkal Tharos na PS (existuje nějaký záznam?), ale to, o čem píše na uvedeném odkazu, zní rozumně. Já jsem spíš pro query objekt než pro větší počet speciálních metod. Mám za to, že s tím bude méně psaní a méně nutnosti si pamatovat pořadí parametrů atp. (což ale nemusí být problém pro někoho, kdo používá nějaké lepší IDE s napovídáním).

Editoval duke (30. 10. 2013 18:23)

před 6 lety

Šaman
Člen | 2275

Tharosova přednáška na Poslední říjnové sobotě. (QueryBuiler se řeší až v závěru v dotazech po skončení přednášky.)
Ostatní budu řešit až večer po tréninku.

Editoval Šaman (30. 10. 2013 18:33)

před 6 lety

Jan Suchánek
Backer | 403

@Šaman: Šlo by do quickstartu přidat nástin řešení propojení dvou tabulek s nějakým tím Query modelem a sortováním? Nebo je správné vše vyřešit na úrovni dibi + Query objectu a vypsat jen Entity z LM a až následně si standartně tahat další data?