Architektura aplikace
28. listopadu 2017
Poznámky rozšiřují informace z článku https://www.zdrojak.cz/…-doctrine-2/. Dobrý souhrn poskytuje i https://fprochazka.github.io/…rchitecture/.
ondrejmirtes [Jan 12th at 1:52 PM]
Tak
třeba:
public function mrdSending()
{
if (!$this->getStatus()->equals(MailingCampaignStatus::IN_PROGRESS())) {
throw new \Slevomat\Mailing\Campaign\MailingCampaignInvalidStatusChangeException($this->getId(), $this->getStatus(), MailingCampaignStatus::SENDING());
}
$this->setStatus(MailingCampaignStatus::SENDING());
}
ondrejmirtes [Jan 12th at 1:53 PM]
setStatus není
public, takže si s tím nemůže dělat každý, co chce
ondrejmirtes [Jan 12th at 2:21 PM]
Jde o to, že je
potřeba mít podchycené operace s entitou. Setter co nic nezvaliduje, je
defacto public property. A ty by už nikdo z vás asi nepsal :)
ondrejmirtes [Jan 12th at 2:22 PM]
nebo třeba když
máte datum od a do, tak nedává smysl na to mít dva oddělené settery.
Musíte napsat jeden, co to zvaliduje
ondrejmirtes [Jan 12th
at 2:30 PM]
nejradši mám, když jsou argumenty metod,
konstruktorů apod. všechny povinné, a píšu na různé usecasy další
metody/třídy
ondrejmirtes [Jan 12th at 2:40
PM]
@jelen07 Tohle je dostatečně obecný princip, který se dá
aplikovat všude. Podle mě všude oceníš to, že si s tvými objekty nejde
dělat cokoli, ale pouze předurčené věci. Pokud se v systému vyskytuje
něco, s čím má jít obecně jakkoli manipulovat, dá se tam udělat
výjimka, ale pouze na tom místě a nikoli všude.
ondrejmirtes [Jan 12th at 2:41 PM]
Jde prostě
o OOP, u kterého si nevytrháš vlasy.
ondrejmirtes [Jan
12th at 3:01 PM]
a kdo ti zajišťuje, že entita, potažmo DB
row, bude obsahovat jen validní kombinaci dat?
brosland
[Jan 18th at 12:20 PM]
Zdravím, tí čo používate 5-vrstvový
model (alebo inak povedané fasády a služby) mám na vás otázku:
Podľa
NetteCamp-2016-doctrine-model som usúdil, že by každá fasáda, ktorá
pracuje s určitým typom entity mala mať metódu
create*(array $values)
, ktorá pomocou služby danú entitu
vytvorí.
Otázka: mala by existovať aj obdobná metóda pre update ⇒
update*(Entity $entity, array $values)
?
Je to okay, ak priamo
v triede formulára nastavím hodnoty do entity pomocou setterov?
ondrejmirtes [Jan 18th at 12:53 PM]
Fasáda by měla
mít metody podle toho, co má dělat :slightly_smiling_face: Pokud fasáda
vůbec entitu nevytváří, nedává smysl metoda create. Pokud jí
neaktualizuje, nedává smysl metoda update :)
ondrejmirtes
[Jan 18th at 12:53 PM]
Jinak k tomuhle tématu doporučuju https://www.zdrojak.cz/…-doctrine-2/
ondrejmirtes [Jan 19th at 7:55 AM]
@brosland: Podle
mě bys do fasády neměl posílat array $values, ale každou hodnotu jako
parametr metody zvlášť, to je daleko hezčí a udržitelnější API
pilec [Jan 19th at 9:09 AM]
btw osobně ještě při
větším množství parametrů je ještě přebaluju do typového DTO
ondrejmirtes [Jan 19th at 9:30 AM]
Tady se
neorientuju ani tak podle počtu parametrů, ale pokud třeba tři věci
předávám furt společně, je čas na objekt :) většinou pak nad nimi
provádím i stejné operace, které se do toho objektu dají dát
ondrejmirtes [Mar 10th at 10:11 AM]
Neměla by jít
vytvořit nevalidní entita. Tzn. všechny povinné argumenty předat
konstruktoru. Dále pak taky – žádné settery!
Entita by měla mít
usecasové metody na všechno, co s ní má jít dělat. (http://www.yegor256.com/…re-evil.html).
Což málokdy znamená setovat do ní úplně cokoli. Každý objekt by měl
zodpovídat za svoji konzistenci, tzn. že i entita. Co se týká ji samotné,
tedy jejího řádku v databázi a případně jejích asociací, by si měla
ověřovat a kontrolovat sama. Jakmile ovšem je potřeba sáhnout do nějakého
externího zdroje, nebo kvůli ověření napsat složitější SQL, už je
validace záležitost servisy.
ondrejmirtes [Mar 10th at
10:12 AM]
Co se týče vícenásobné validace, tomu se asi nedá
úplně vyhnout. Validace v modelu by měla být povinná – protože by
model neměl jít rozbít. A záleží, kdo pak ten model používá –
jestli webové formuláře, nebo API, nebo CLI. Pro každého takového
uživatele je validace dost specifická. Třeba pro CLI lze další vrstvu
validace úplně vynechat, protože tam nevadí, že vyletí výjimka z modelu
až do outputu.
ondrejmirtes [Mar 10th at 10:13
AM]
Ono vlastně u validace záleží jen na tom, jak rychlý
chcete dát feedback uživateli. Jestli nevadí, že selže odeslání
formuláře a vykreslí se chyba při dalším načtení, stačí mít validaci
v modelu a chytat výjimky. Pokud chcete rychlejší feedback, musíte
implementovat validaci v JS…
oli [Mar 10th at 10:17
AM]
@ondrejmirtes díky. Mě se to takhle líbí a takhle bych to
chtěl. No, neprosadil jsem to :slightly_smiling_face:
Hlavní problém,
který máme je v konstruktoru. Pokud má entita 4 povinné parametry, tak
jsou v konstruktoru a je to třeba jméno, příjmení, rc a email. Pokud bude
spatne rc a email, tak jak oba tyto parametry vypises uzivateli naraz ze jsou
rozbity? pokud vyhodis vyjimku, tak se k tomu dalsimu uz ani nedostanes. a
v konstruktoru vlastne ani nevytvoris objekt…
ondrejmirtes [Mar 10th at 10:19 AM]
@oli Konzistence
modelu a výpis chyb uživateli jsou úplně oddělené problémy, tak bych se
k nim tak choval.
ondrejmirtes [Mar 10th at 10:28
AM]
Každý objekt zodpovídá za svou konzistenci a
zapouzdřenost. Tímhle bych začal, třeba se ti pak povede přijít
s nějakým lepším řešením, ale z tohohle bych neslevoval.
ondrejmirtes [Mar 10th at 10:32 AM]
K zamyšlení – u stringu ti typicky stačí povinnost/nepovinnost.
Jakmile někde potřebuješ ověřovat konkrétní tvar – RČ – mělo by
to být na zvláštní objekt. Když si pak v entitě říkáš o objekt RČ,
už víš, že bude validní.
ondrejmirtes [Mar 10th at 10:37 AM]
$rc = new PersonalNumber('801111/0654'); // vyhodi vyjimku, pokud je spatne
public function __construct(PersonalNumber $rc) // uz vim, ze je validni
{
}
oli [Mar 10th at 10:39 AM]
jeste, kdo by mel
validovat jestli je string mezi min, max? Service?
ondrejmirtes [Mar 10th at 10:40 AM]
entita
ondrejmirtes [Mar 10th at 10:40 AM]
pokud ale
potrebujes treba overit, ze je DIC v databazi unikatni, tak uz to entita sama
nezvladne, musi servisa
oli [Mar 10th at 10:40
AM]
a pokud je to v konstruktoru?
oli [Mar
10th at 10:40 AM]
tak to pri jmene, ze je kratke spadne, ale ze je
spatne prijeni uz nezjistim
ondrejmirtes [Mar 10th at 10:45
AM]
pokud mas overovani v entite, tak v servise to znovu neni
potreba… jen pokud chces tu chybu reportovat uzivateli, v typicke CRUD
aplikaci, tak nastav formular tak, aby to overovani v entite nespadlo, tedy aby
tam nepropluly spatny data
ondrejmirtes [Mar 10th at 10:45
AM]
v M (modelu) staci ta validace jednou, logicky
oli [Mar 10th at 10:46 AM]
to mam namysli. bral jsem
to ze to je jedno jestli ve formulari nebo v service.
ondrejmirtes [Mar 10th at 10:47 AM]
servisa je
soucasti modelu a v modelu uz to overeni mas, v entite
ondrejmirtes [Mar 10th at 10:51 AM]
kdyz si rekne
o objekt RC, tak tam nedovoli predat neco spatne
:slightly_smiling_face:
ondrejmirtes [Mar 10th at 10:51
AM]
premyslet je potreba hlavne pri navrhu rozhrani te entity…
hezke na tom pak je to, ze ti nedovoli udelat chybu
ondrejmirtes [Mar 10th at 10:52 AM]
pokud tam
predavas neco, co nemas, tak aplikace spadne, ale to nevadi, hlavne, ze se
neposkodila data
ondrejmirtes [Mar 10th at 10:52
AM]
mrknes na chybu v logu a validaci si opravis, to je
v pohode
ondrejmirtes [Mar 10th at 11:12 AM]
Ne, uživatel uvidí obecnou 500ku nebo flashku „něco se
pokazilo“.
ondrejmirtes [Mar 10th at 11:12 AM]
Text výjimky do rukou uživatele nikdy :skull:
ondrejmirtes [Mar 10th at 11:12 AM]
Chyba přijde do
logu, kde si ji vývojář přečte a nekonzistenci opraví. Reálně se to zas
tak často neděje.
ondrejmirtes [Jun 14th at 2:24
PM]
Důležité prvky architektury:
- Controller – přijme request, zvaliduje správné typy a hned ho pošle do fasády. Datetime jako value objekty, identifikátory entit jako skaláry. Dále handluje výjimky vyhozené fasádou.
- Fasáda – přijme parametry z controlleru, shání entity přes repository, volá servisy s business logikou, persistuje a flushuje, stará se o DB transakce.
- Servisy – obsahují unit testovatelnou business logiku s hezkým inputem a outputem. Tohle je volitelná část architektury, pokud nechceš testovat, můžeš ten kód mít přímo ve fasádě. Ale osobně mi to přijde dost důležité.
- Repository – shání data v databázi a dalších úložištích (filesystém, redis atd.).
- Entity – reprezentuje řádek v databázi, zodpovídá
za jeho konzistenci a případně za konzistenci navázaných asociací. Nesmí
být rozbitelná, tedy povinné parametry v konstruktoru + ne settery, ale
usecasové metody alá
approve
,publish
,reject
atd. Vyhodí výjimku, pokud se provádí nepovolená operace nad jejím stavem.
ondrejmirtes [Jun 14th at
7:43 PM]
@ondravotava Záleží, co s tou entitou děláš.
Neumožňoval bych s ni navenek dělal cokoli, protože ne všechno vždy jde
dělat. Některé hodnoty taky souvisí spolu. Pokud máš třeba v entitě
pole dateFrom a dateTo, tak bych na to napsal jeden setter setDates($from, $to)
a zkontroloval bych, že $from < $to. Různé stavy a workflow entity bych
nedělal přes public setStatus, ale přes use-casové metody jako approve,
reject, které uvnitř kontrolují, že to, co chceš, zrovna jde udělat…
Spousta metod taky třeba kromě aktualizace nějaké hodnoty chce zároveň
měnit vnitřní modifiedTime, třeba…