Entity
- zapouzdřuje data
- zodpovídá za svoji konzistenci, validuje si přijímaná data, není anémická
- v každém momentu své existence je validní
- AnemicDomainModel by Martin Fowler
- Anemic domain model on Wikipedia
- Hlavní důvod proč nechceme takové entity je, že anémický model popírá principy OOP, viz zkrácené “Liabilities” na wikipedii.
- Nemá zbytečné settery, místo toho pokud je potřeba měnit její stav, má metody které nejprve validují, že taková změna je možná.
- Validuje pouze věci, které je možné ověřit bez sahání do databáze nebo někam “mimo vzduchem” - tedy například nemůže ověřit, že neexistuje jiný uživatel se stejným emailem.
- Je nezbytné, aby byla konzistentní už v momentě vytvoření
- nemá autogenerované
id
hibernatem
- Má UUID, které získá už v konstruktoru => není závislá na přiřazení idčka od hibernate.
- Pokud bych chtěl implementovat
equals()
a hashCode()
, použiji k tomu pouze field id
(UUID plněné v konstruktoru)
- je persistence agnostic/ignorant
- její identita je daná její existencí
- Classification by Eric Evans on Martin Fowler’s blog
- Tak jako v reálném světe jsem já jako jedinec uníkátní z principu toho že existuji, tak stejnou vlastnost má i entita a nějaký identifikátor je pouze implementační detail srovnatelný s mým rodným číslem. Ovšem budu existovat i bez rodného čísla a nikdy nebudu stejný jako někdo jiný.
- Entity nikdy nebudeme porovnávat přes bussines equivalence, ale pouze přes reference (fakt že ukazují na stejné místo v paměti).
- Mohlo by se zdát, že dvě entity jsou stejné pokud mají stejné
id
a typ, ale to není tak jednoduché. Dva objekty jsou stejné pouze pokud mají stejnou identitu a v případě že je načítáme z databáze tak budou stejné pouze v případě, že mají stejné nejen id
a typ ale navíc je vrátila stejná instance EntityManageru, která by měla po dobu běhu transakce být neměnná.
Services
- neví o databázi
- díky tomu budou jakékoliv testy služeb velice rychlé
- pokud k otestování služby potřebuji databázi, není to unit test ale integrační test
- trully unit testy by to samozřejmě byly pouze v případě, pokud bychom poctivě mockovali všechny závislosti třídy - což dělat nebudeme z pohodlnosti, ovšem pokud by nastala situace, že se to někde hodí, tak můžeme
- může používat jiné služby
- může vytvářet entity, takové entity pak vrací jako výsledek svého volání
- může vracet entity s informací, že se mají smazat
- může kontrolovat oprávnění
- kontroluje pouze věci, které neumí zkontrolovat sama entita
- například že uživatel v databázi je unikátní, ovšem na databázi nesahá, místo toho si nechá předat nullable argumentem případného již existujícího uživatele se stejným emailem od fasády
- nesmí sahat na fasády ani repozitáře
- není nutné, aby odpovídaly 1:1 entitám, repozitářům nebo fasádám
- je striktně bezstavová
- další výhody:
- maximální znovupoužitelnost i v rámci jiných services
Repositories
- obsahují pouze
find*
a get*
metody, které pouze čtou
- pokud je metoda
find*
, může vracet null a nikdy neháže výjimky pokud výsledek nenajde
- pokud je metoda
get*
, nesmí vracet null a pokud výsledek nenajde, tak háže výjimku
- striktní zákaz volání jakéhokoliv
persist()
, flush()
, clear()
, merge()
a dalších metod na modifikaci entit nebo čištění identity mapy
- nikdy nepřijímá v argumentech entity, ani když podle nich potřebuje hledat
- místo toho přijímá vždy jejich identifikátory - častěji mám k dispozici
id
entity, než entitu samotnou
- pokud není nutné kontrolovat oprávnění a pouze se čtou data, je možné pro zachování jednoduchosti volat repozitář přímo v controlleru, cli, atd
- není nutné, aby odpovídaly 1:1 entitám, službám nebo fasádám
Facades
- veřejné rozhranní aplikace
- metody odpovídají jednotlivým use-cases, které je v aplikaci možné provádět
- slouží jako znovupoužitelná horní vrstva abstrakce nad modelem - je možné použít v controlleru, cli rozhranní, restovém api, cronech, …
- obsahuje co nejmenší množství logiky, v ideálním případě vůbec žádnou
- využívají služby, repozitáře a entity manager
- z repozitářů načítá data, které vyžaduje služba ke svojí práci
- řídí persistenci entit
- pokud služba kterou fasáda volá vytvoří novou entitu, fasáda ji zapersistuje
- pokud služba kterou fasáda volá vrátí entitu s tím že má být smazána, fasáda ji smaže
- pokud operace samotná nevyžaduje logiku (kontrolu oprávnění, validace, …) není nutné volat služby
- například mazání většího množství entit je lepší udělat jedním
DELETE ...
HQL dotazem na úrovni fasády
- přijímá buď surová data, nebo struktury (v třídách, které jsou definované ve stejném namespace)
- může vracet buď specifické result objeky, nebo entity
- entity zodpovídají za svůj stav - nevadí tedy, že je vypustíme mimo fasády
- není nutné, aby odpovídaly 1:1 entitám, službám nebo repozitářům
Controllers
- zodpovídá za namapování HTTP requestu na struktury, se kterými pracuje fasáda
- pokud pouze čte nějaká data a není nutné ověřovat oprávnění, nevadí příme použití repozitáře
- zodpovídá za reprezentaci odpobědi
- v případě použití šablony nevadí když entity pošleme do šablony
- v případě rest api využívají specifické
*ResponseFactory
třídy, které zodpovídají za mapování výsledku volání fasády (buď result objekt nebo entity) na struktury vhodné pro prezentaci ven (např json)
Společné
- striktní dodržování Inversion of Control