Kam přesně psát SQL dotazy?
- Facedown
- Člen | 38
Ahoj, již delší dobou mě trápí kam přesně psát SQL dotazy. Dělám to tak, že zvlášť pro každý presenter mám třídu, která se stará pouze o SQL dotazy. Ukázka:
interface NotificationStorage {
public function register($id);
public function unregister($id);
}
class NotificationDatabase implements NotificationStorage {
public function register($id) { /* SQL */ }
public function unregister($id) { /* SQL */ }
//.....
}
class NotificationRepository {
private $storage;
public function __construct(NotificationStorage $storage) {
$this->storage = $storage;
}
public function register($id) {
$this->storage->register($id);
}
public function unregister($id) {
$this->storage->unregister($id);
}
}
class NotificationPresenter extends Presenter { }
Interface slouží jako kontrakt tak abych mohl kdykoliv použít jinou databázi, či ukládat třeba do textového souboru, to je už jedno. NotificationDatabase je implementace konkrétního úložiště. NotificationRepository je implementace, kterou používám v presenterech či v komponentách.
Nevím ale, zda je to tohle dobrý způsob, protože interface se mi může změnit takřka kdykoliv a nemám jistou, že zůstane stejné, což je pochopitelné. Za další se mnohdy mohu také setkat s duplicitou kódu, protože zvlášť pro každý presenter mám jednu třídu, která se mi stará o dotazy. Na druhou stranu se mi to zase zdá fajn, protože používám pouze to, co potřebuji a nic víc.
Stejně se mi to zdá docela chaotické, a proto bych rád věděl, jak to děláte nebo jste dělali vy ostatní. Nějaký nástroj se zatím použít nechystám, protože napřed bych rád věděl, jak to udělat v „čistém php“.
Děkuji za názory.
- Šaman
- Člen | 2666
Viděl jsem ORM (od Petra Procházky), které ve skutečnosti nebylo ORM, ale OSM – tedy Object-Storage Mapper. Na objektové straně se používal jako ORM, ale bylo možné si data ukládat i mimo databázi. Na jedné poslední sobotě byla dobrá přednáška o pětivrstvém modelu, což je stejná architektura jako v tomto ORM. Tam měl každé úložiště na starosti konkrétní mapper a repozitáře s ním komunikovaly přes pevné rozhraní. A to se jen tak nemění.
Pokud se ale ptáš jen na SQL dotazy (tj ignoruješ ostatní úložiště), pak stačí buď jednoduché repositáře (a případně fasády nad nimi) které pracují nad databázovou vrstvou, nebo komplexní ORM, ať už Doctrine, nebo jeden z mnoha malých.
A trochu nechápu, proč tam zmiňuješ presentery. Jaké dotazy pro presentery? Bavíme se přece o modelu, ten o nějakých presenterech nemá ani tušení. Presentery si jen vyžádají model a všechno delegují na něj. Nebo máš úplně jinou architekturu?
// edit: Ajo, přednášku o pětivrstvém modelu neměl Petr Procházka, ale Jan Tichý.
Editoval Šaman (21. 6. 2015 2:20)
- Facedown
- Člen | 38
@Šaman Děkuji za odpověď. O tom pětivrstvém modelu jsem již také četl – ať už na Planette nebo u Jana Tichého.
Mám třeba stránku s notifikacemi. Ty si mužu zobrazit, odebrat, přidat do oblíbených a plno dalších operací. Cíleně pro tuto stránku s těmito operaci si vytvářím metody s SQL dotazy – SELECT, DELETE, INSERT. Pracuji poté s Repository, do kterého pomocí kontruktoru předávám tento objekt s SQL dotazy. Třídu Repository poté používám v komponentách nebo v presenterech.
Snad si mě pochopil. Pokud to dělám špatně, budu rád za tvou radu.
- Azathoth
- Člen | 495
@Facedown ona ta myšlenka je spíš taková, že by to mělo jít
(podle mne): presenter->model (aplikační logika)->model (databázová
vrstva, sql dotazy, apod.). A i když teď (a u malých aplikací možná
i napořád) voláš metodu s nějakým jendím sql pouze v jednom
presenteru->tedy pro každý presenter máš třídu na sql dotazy, tak se ti
při růstu aplikace bude stávat, že ten samý kus modelu (např. CRUD nad
notifikacemi) budeš používat ve více presenterech.
A proto Šaman nechápe, proč tam explicitně uvádíš, že každý presenter
má „svou“ modelovou třídu.
Takže pokud dva presentery pracují s tou samou částí aplikace, těmi
samými daty, není žádný důvod, proč mít pro každý jinou třídu s sql
dotazy. To by ti mělo eliminovat právě tu duplicitu kódu, které se
obáváš.
A změna interface je podle mne logická věc. Spíš bych se divil, kdyby se interface během vývoje vůbec neměnil.
Jinak logika: presenter->repository->storage se mi líbí a já sám ji také přibližně používám. Sice mám doctrine, ale snažím se mít mezi vrstvou (třídami) s DQL, entity managerem, apod. vrstvu, která řeší čistě logiku a nepracuje explicitně s databází/ORM, což je v tvém případě Repository, jestli to správně chápu.
A co se ti na tom jinak zdá chaotické?
Editoval Azathoth (21. 6. 2015 2:13)
- Facedown
- Člen | 38
@Azathoth Skvěle si mě pochopil. Chaotické se mi na tom zdá to, že stále hledám správný způsob jak na to a stále na něj nemůžu přijít. Jak píšeš, také se mi nelíbí to, že se někdy střetnu s duplicitou kódu. Nicméně, společné funkce se snažím vyčleňovat do samostatných tříd. Duplicitou kódu tak trpím pouze v případě private metod.
Pokud bych si zase třídu vytvořil nad konkrétní DB tabulkou a tam narval vše co budu používat se mi taky nezdá pěkný.
Takže ty si taky vytváříš třídu s SQL dotazy, která má na starosti daný presenter? V případě, že používáš ORM to může být asi odlišné, ale jinak to směruješ stejně?
Editoval Facedown (21. 6. 2015 2:29)
- Šaman
- Člen | 2666
Facedown napsal(a):
Mám třeba stránku s notifikacemi. Ty si mužu zobrazit, odebrat, přidat do oblíbených a plno dalších operací. Cíleně pro tuto stránku s těmito operaci si vytvářím metody s SQL dotazy – SELECT, DELETE, INSERT. Pracuji poté s Repository, do kterého pomocí kontruktoru předávám tento objekt s SQL dotazy. Třídu Repository poté používám v komponentách nebo v presenterech.
Snad si mě pochopil. Pokud to dělám špatně, budu rád za tvou radu.
Aha, tak to bylo jen trochu zmatení pojmů. Podle mě totiž nemáš stránku s notifikacemi, ale část modelu, která řeší notifikace. Tak sis na to udělal repository, na složitější logiku případně fasádu nad tím repository. Většinou je to nad jednou tabulkou, ale to není vůbec podmínka, důležité je, že je to nad jednou entitou, nebo nad skupinou těsně propojených entit. A tomu já říkám model, nikoliv stránka, nebo presenter. To je už jedno, kde a kolikrát to použiješ.
Jinak myslím, že problém je v tom, že chceš po úložišti, aby ti
(v ukázce výše) registroval uživatele. To má (podle pětivrstvého modelu)
dělat fasáda (u jednoduchých modelů to mám přímo v repository, ale je
to trochu ojeb). Proto ti asi vznikají duplicitní kusy kódu. Předpokládám,
že registrace uživatele znamená třeba nastavit mu nějaký atribut, že je
registrovaný. To řeší právě ta fasáda, která použije repozitář
k tomu, aby si načetla uživatele, pak mu nastaví ten atribut a pak ho zase
pomocí repozitáře uloží. A ten tvůj StorageInterface by pak měl být
jednotný a řešit jen CRUD operace pro různá úložiště.
Jednoduché, čisté… ale neefiktivní u určitých případech.
Právě proto, že nikam nepíšeš SQL dotazy. SQL ti pak slouží jen k primitivnímu ukládání a načítání záznamů. Zbytek děláš nad kolekcí objkektů v nějaké vrstvě, která o existenci databázi vůbec neví (ta databáze tam totiž být vůbec nemusí).
⇒ Takže je otázka, jestli potřebuješ ta ostatní úložiště a pokud ano, jak často. A potřebuješ je často vyměňovat? Pokud jen občas pro specifické činnosti (třeba jen zjištění, jestli nemáš daný objekt v cache a pokud ano, tak ho cely vrátit), pak bych doporučoval si na tyto činnosti vytvořit samostatnou třídu a zbytek modelu psát s vědomím, že jedeš nad databází. Takže klidně přímo v repository si můžeš psát složitější SQL dotazy (nebo DibiFluent, nebo Nette\Database\Table fičury – obě vrstvy ti umožní změnit engine databáze, ale ponechají ti většinu výhod relační databáze).
Pokud opravdu potřebuješ univerzální mapper mezi objekty a obecnym úložištěm, pak věz, že řešíš jednu z klasických úloh, na kterou neexistuje žádné nejlepší řešení. Existuje spousta řešení, ale každé má své mouchy. Buď je moc specifické pro jeden typ problému, nebo je moc složité, nebo není efektivní, zkrátka bude to vždy kompromis. V takovém připadě to řešení, které používáš je jedno z nejjednodušších a na malé aplikace dostačující. To interface se pak logicky mění a zaručuje ti, že všechna úložiště umí uspokojit potřeby konkrétního repozitáře.