Budeme-li brát v úvahu i "Vícevrstvé architektury", jsou podobné principy návrhu architektury systémů známy již desítky let. Věřím, že drtivá většina studovaných programátorů se jimi řídí a již desítky let se snaží vrstvy oddělovat. V oboru jsem skoro už dvacet let. Pracuji převážně na velkých informačních systémech, které dané principy dodržují/dodržovaly.
Zatím jsem nezažil, že by si někdo troufnul vyměnit jednu z těch vrstev v obalu té cibule s tím, že zbytek se nechá skoro beze změny. Např. výměna prezentační vrstvy ve velkém systému je skoro nemožná, hlavně proto, že prakticky je i v prezentační vrstvě spousta business logiky (např. "pokud zaškrtnu tenhle checkbox, tak schovej tyhle dvě políčka" - v informačních systémech takováhle pravidla tvoří troufnu si říct vlastně většinu všech business rules). Nebo pokud někdo napíše danou aplikaci pro konkretní databázi (např DB2), tak i kdyby jste měli všechna DML a DDL v SQL standardu, tak vám to s jinou databázi prostě fungovat nebude (respektive ono se to rozjede, ale dáte ruku do ohně za to že to funguje naprosto stejně? :)).
Z mojí zkušenosti se ke starším systémům buď začal přilepovat kód v nové technologii (např souběh dvou prezentačních vrstev) a nebo se celý systém vzal a přepsal se komplet do nových technologií. Pokud někdo někdy zažil něco jiného prosím napište níže - mám na mysli systémy velikosti řádově statisíců řádek, fungujících mnoho let.
Takže s článkem souhlasím, s vrstvením souhlasím, ale pouze pro účely snadnějšího porozumění systému jako celku. Vrstvení za účelem možné "snadné" výměny nějaké části je teorie, kterou jsem v praxi opravdu nezažil a nemyslím si že je možná.
Čím jsem starší, tím víc mám pocit, že se stačí držet SOLID principů (https://en.wikipedia.org/wiki/SOLID) nejenom v návrhu tříd, ale i v návrhu modulů či integrace systémů... zbytek tak nějak vyplyne sám...
24. 1. 2020, 08:28 editováno autorem komentáře
@wajtr
Tak jest. Skoro každá déle exitující firma to tak má. Málokterá má čas peníze a chuť pořád všechno přepisovat a ještě dostatečně často. V momentě kdy i celou aplikaci zahodí a napíše znova, jako tuším třeba Aukro, tak se nevyhne tomu co píšete a od určité chvíle se iz toho nového systému stává to co píšete.
Každopádně bych pro Vás měl jeden běžný příklad užití vrstvení za účelem výmeny technologie - jsou to databázové mappery jako Doctrine (php) nebo Hibernate (Java) které pokud jsou správně implementovány do aplikace (persistence, repository, facade, ...) umožňuji kompletní výměnu datového úložiště - přímo a prakticky se tak děje např. u Doctrine kdy pro účely automatizovaného testování použijete SQLite DB a pro aplikaci třebe MariaDB nebo MySQL. Abych byl férový, narazili jsme na jednu (ale jedinou) obezličku kterou jsme kvůli použití SQLite pro testy museli vyřešit v jednom Repozitáři.
Samozřejmě i tak je to u složitější aplikace často něco za něco.
24. 1. 2020, 09:16 editováno autorem komentáře
Jo, jenže proč bych je měl spouštět po každé změně? Od toho tu je právě oddělení na doménový model, infrastrukturu, kam patří repositories a aplikační vrstvu (use-cases). Když už, tak tyhle testy oproti DB nakonec pouštím na CI/CD serveru. Pokud se hrabu v doménovém modelu, vůbec mě tohle nezajímá víz pojem "Persistence ignorance"
24. 1. 2020, 16:27 editováno autorem komentáře
@uetoyo
Pokud vyvíjíte složitou aplikaci s velmi složitými ? obsáhlými daty, ke všemu ješté od píky, mockovat MB odpovědí v rúzných kombinacích je fakt opruz a ztráta času v týdnech. Moct si spustit testy (třeba celou složku) při psaní nějaké složitejši logiky bez penalizace kolikrát chcete v čase 1s nad reálnou DB je k nezaplaceni - ne vzdy jenom čtete a používáte jednu entitu bez vazeb, že ....
Nehledě na to že když si neco do té Vaší oddělené vtrstvy namockujete a na druhém konci aplikace data zmenite, tak tu chybu objevíte už pri vývoji, ne az po commitech a cekánim na CI/CD testy a vracenim se od dalsi rozdelane prace. A dalsim kolečkem mockováni a CI/CD.
Jinak spouštění určité části testů při každé změně je jedna z běžných praxí, Test Driven Development.
24. 1. 2020, 23:02 editováno autorem komentáře
"Jinak spouštění určité části testů při každé změně je jedna z běžných praxí, Test Driven Development." Jasně, ale když máte oddělené vrstvy např. doménovou, tak nepouštím vedle jednotkových testů i integrační po každé změně na svém lokálu. Ještě jinak ...když změna náleží doménové entitě (entitám) tj. operuje jen nad ní, nezajímá mě persistence tj. integrační testy. __TDD není o tom pouštět všechny testy, které navíc vůbec nesouvisí se změnou, kterou jsem udělal__.
25. 1. 2020, 11:57 editováno autorem komentáře
@uetoyo
"Jasně, ale když máte oddělené vrstvy např. doménovou, tak nepouštím vedle jednotkových testů i integrační po každé změně na svém lokálu"
Však to taky nikdo netvrdil. Ale ne vždy pracujete jenom s jednotkami, že? Např. při hledání problému nebo jejich zkoušením ... Pokud děláte něco složitějšího než "Rabit::sayHello" často pracujete opakovaně s funkcionálními testy. Např. když budujete větší API - jednotky si sice napíšete, ale k čemu je Vám endpoint když si nezkusíte že Vám skutečně něco vrací - což je význam funkcionálních, resp. integračních*, testů. Kruh uzavřen.
*Často se rozdíl stírá s komplexitou a variabilitou aplikace, AFAIK neexistuje jednoznačné rozlišení pro každý případ. Teorie a definicí je samozřejmě hromada, není nutno sem nějakou rychle vygooglenou kopírovat.
"Ještě jinak ...když změna náleží doménové entitě (entitám) tj. operuje jen nad ní, nezajímá mě persistence tj. integrační testy. __TDD není o tom pouštět všechny testy, které navíc vůbec nesouvisí se změnou, kterou jsem udělal__."
1) To nikdo netvrdil
2) Až budete opravdu pravidelně a poctivě testovat zjistíte, že "testy, které navíc vůbec nesouvisí se změnou" je celkem pravidelný zdroj překvapení co všechno je možné rozbít na různých koncích aplikace.
25. 1. 2020, 12:15 editováno autorem komentáře
'Až budete opravdu pravidelně a poctivě testovat zjistíte, že "testy, které navíc vůbec nesouvisí se změnou" je celkem pravidelný zdroj překvapení co všechno je možné rozbít na různých koncích aplikace."' Ještě jednou: některé testy pouštíme na CI/CD. Nemá smysl je pouštět na lokále. Ano, pokud dělam změnu která řeže projekt vertikálně, tak se to samozřejmě může dotknout různých částí aplikace. Nevím z čeho hned vyvozujete, že někdo pravidelně a poctivě netestuje.
@uetoyo
Z čeho tak vyvozuji? Vyvozuji tak z toho co tu tvrdíte. Např. ten Váš "vertikální řez" - když se nad tím zamyslíte, tak pokud nemáte jednovrstvou aplikaci, tak Váš kód v oné vrstvě bude mít vždy nějaký vertikální dopad. A je to přesně obsahem oné poznámky:
Až budete opravdu pravidelně a poctivě testovat zjistíte, že "testy, které navíc vůbec nesouvisí se změnou" je celkem pravidelný zdroj překvapení co všechno je možné rozbít na různých koncích aplikace."
Spletl jsem se ale v jedné věci. Na toto není ani potřeba mít žádné zkušenosti s testováním. To, co je obsahem zmíněného výroku, je přesně důvodem velmi častých problémů nasazování netestovaného kódu do produkce a častým zdrojem překvapení zejména mladých programátorů - že úpravou nějaké malé blbosti v jedné vrstvě rozbili nějakou funkcionalitu v úplně jiné vrstvě o 50 souborů vedle, přestože se pokusili po opravě kód ještě prověřit fulltextem IDE.
Nikdo Vás taky nenutí pouštět všechny testy úplně pokaždé, ale většinou (mrkněte do manuálu) se dají pustiti testy jenom pro nějakou jednu dvě složky, něco jako např.
tests/[func|unit]]/{*}HandlingService (pokud to teda nemáte všechno úplně na jedné hromadě) a nebo se dá často filtrovat podle názvu, např. něco jako
./run --filter "Users|Aeroplane"
či třeba jenom
./run tests/functional/services/someService::testHandleSomething což ale může zahrnovat i dalších x vrstev, y souborů a hromadu dat - přes loginy a externí služby .... je dobré moct si vyzkoušet hned na místě že to co bylo zaneseno do testů funguje i po opravě. Napsal jsem dobré, ale je to úžasné, protože mezi tím nemusíte pootvírat dalších milion blbin k další věci než Vám dojede CI/CD a vy zjistíte že jste něco nedomyslel.
A nebo si můžete napsat funkcionální test pro novou metodu a jeho opakovaným spouštěním dokonce zjišťovat jak aplikace funguje, co Vám různé služby vrací a postavit tak úplně novou funkcionalitu (Test Driven Development) nebo prokázat že to zatím nejde a můžete to každému kolegovi jako proof of koncept s užitím debugeru a datových fixtur nechat ukázat třeba 1000x za sebou - a když Vám to pak třeba opraví tak to můžete zrovna nechat jako test - aniž byste se k tomu např. musel pořád proklikávat dokola nějakým rozhraním a lovit nějaké něco někde po lozích a konzolích nějakých aplikací a browserů .... To v CI/CD budete dělat těžko ;-)
25. 1. 2020, 17:55 editováno autorem komentáře
Z čeho tak vyvozuji? Vyvozuji tak z toho co tu tvrdíte. Např. ten Váš "vertikální řez" - když se nad tím zamyslíte, tak pokud nemáte jednovrstvou aplikaci, tak Váš kód v oné vrstvě bude mít vždy nějaký vertikální dopad.
To je nesmysl. Servisní funkce co provádí nějakou kalkulaci, ovlivní ukldádání do DB? Každá metoda co náleží nějaké entitě ovlivní hned všechny další vrstvy aplikace? Prosím stáhněte si nějakou knihu o DDD. Osobně nemám v době dockeru problém si pustit testy i nad DB lokálně, ale často to stačí až na CI/CD serveru. Pěkný večer přeji.
26. 1. 2020, 20:54 editováno autorem komentáře
@uetoyo
Hmmm, takže nezamyslel.
* Servisní funkcí předpokládám myslíte funkci v nějaké službě (třídě)
1) V textu který citujete je věta tak pokud nemáte jednovrstvou aplikaci. Bavíme se o apliaci a jejich vrstvách a o tom Vašem "vertikálním řezu", vzpomínáte? Je to opět i v té citaci kterou jste uvedl.
2) To že najdete nějakou konkrétní funkci která nedělá nic s databází nic neznamená, použitá může být na spustně jiných míst a už vůbec to neznamená že jsou takové funkce všechny. I tato Vaše funkce bude nějaký další kód ovlivňovat, pokud to není 'main' funkce s výpisem do konzole z HelloWorld Tutoriálnu. Minimálně bude Vaše aplikace mít nějakou zobrazovací vrstvu.
3) Je hezké že si pouštíte testy i lokálně. Tak proč píšete věci jako
eště jednou: některé testy pouštíme na CI/CD. Nemá smysl je pouštět na lokále. Ano, pokud dělam změnu která řeže projekt vertikálně, tak se to samozřejmě může dotknout různých částí aplikace.
a teď ještě přinášíte příklad který by snad neměl ani vertikální rozměr mít?
Co se mi tu vlastně snažíte tvrdit? Že běžné jsou aplikace které nemají vrstvy a testování po změnách na localhostu je nesmysl?
Díky už nám to asi všem stačilo -- mě už určitě. Máte v tom jasno, není třeba to dál rozebírat. Půjčtě si knihu, třeba tuhle: https://pragprog.com/book/swdddf/domain-modeling-made-functional nebo tuhle https://www.manning.com/books/functional-and-reactive-domain-modeling
27. 1. 2020, 21:37 editováno autorem komentáře
A ještě naposled pro vás:
> These tests are slower than in-memory unit tests, but that’s ok. They don’t need to be executed as often as unit tests. Only when persistence logic changes occur which usually happens more rarely than domain logic modifications.
http://www.taimila.com/blog/ddd-and-testing-strategy/
27. 1. 2020, 22:13 editováno autorem komentáře
@uetoyo
Ale já tyto věčné debaty ohledně strategie testováni znám. Samozřejmě na to mám svůj názor, argumenty a znám i spoustu argumentů ostatních stran. Tak argumentuji, nemusím se schovávat za obecné odkazy a tvrzení demonstrované na urcitých řekněme specifických pripadech. Dokonce existují i názory že stačí jenom plné unit testy, někomu stačí jenom code coverage a dokonce jsem nedávno mluvil s člověkemz jiné firmy který se přiznal že nedavno na pohovoru rekl ze testy jsou ztrata času - vsak vi co měnil. To vám neprisuzuji, to jenom abyste videl že tezí je mnoho, já měl za to ze se bavime o reálné praxi a zkusenostech.
Jako myslete si o mě co chcete, ale z denodenní praxe testování vím, že se celkem často chyba nebo zmena projeví i vvdalsich vrstvach aplikace - ono je to taky logicky tím že ty tvrsvy a jejich akce jsou na sebe navázané.
No a speciálně pro Vás: Začnete mluvit o "řezu vrstvami", občas to vztáhnete jenom na vrsrvu persistence (jako v itaci vyse) a příklad dáte s funkcí v nějaké evidentne jednovrstvé implementaci. Asi byste si ty okazy měl projít nejprve sám a pak to vyzkouset i prakticky. Já Vám prakticky priklad dam.
Nedavno jsem opravoval funkci v jedné službě (třída), unit testy jsem upravil, pustil jsem functionalni testy podle jmena (asi 3) a zjistil jsem, ze v jine metode kde je tato funkce pouzita v jednom prípade nastane situace ktera me nenapadla. Kdybych to chtěl zkoušet normálně v dev instanci tak budu muset naklikat asi 4-6 scénářů. Takže behem minuty jsem to zjistil, pak opravil a nechal při MR jet v pipeline testy 25 minut. Tak Vám děkuji za praktickou radu nepustit func. test na localhostu a cekat ? času na pipeline a mezitím někde začínat neco druhého a pak snad i třetího a kdovi kolikátého. Dekuji, rozhodně se Vaší rady drzet nebudu
"Jako myslete si o mě co chcete, ale z denodenní praxe testování vím, že se celkem často chyba nebo zmena projeví i vvdalsich vrstvach aplikace - ono je to taky logicky tím že ty tvrsvy a jejich akce jsou na sebe navázané." Může být, to nepopírám, ale při vhodném vrstvení se to nemusí stávat často. A to vám tak vadí, že vám takový fail odchytne CI/CD server?
Já totiž jen tvrdil , že některé testy nemá smysl pouštět u sebe, že se odchytnou na CI/CD serveru. Pokud tedy jsou napsané. Což tedy i podporuje tu vaši zkušenost, že jste něco nedomyslel a nakonec to spadlo na nějakých integračních/funkčních testech. To spouštíte např. testy pro všechny verze interpreteru na lokále? Od toho právě máme CI/CD. A tady nám reálně věci padají např. na různých verzích Pythonu apod. nebo dokonce někomu na různých verzích OS. To, že to všechno projde u Vás ještě vůbec nic neznamená. Ale to bude nekonečná debata co? Já to zkrátím, protože vaše texty se prodlužují.
@uetoyo
A) "Může být, to nepopírám, ale při vhodném vrstvení se to nemusí stávat často."
Tak s tím se dá souhlasit. Každopádně se to dá pomocí testů velice jednoduše zkontrolovat - ano, dokud neproběhnou všechny testy, je to lehce indikační, dá se něco minout, to nezastírám.
B) [1] "Já totiž jen tvrdil , že některé testy nemá smysl pouštět u sebe, že se odchytnou na CI/CD serveru."
Dovolte abych Vám odcitoval
[2] "Jo, jenže proč bych je měl spouštět po každé změně? Od toho tu je právě oddělení na doménový model, infrastrukturu, kam patří repositories a aplikační vrstvu (use-cases). Když už, tak tyhle testy oproti DB nakonec pouštím na CI/CD serveru. Pokud se hrabu v doménovém modelu, vůbec mě tohle nezajímá víz pojem "Persistence ignorance" " [ 24. 1. 2020 16:24 uetoyo]
S [1] bych souhlasil a pravidelně to dělám. S [2] bych nesouhlasil.
C) "To spouštíte např. testy pro všechny verze interpreteru na lokále? Od toho právě máme CI/CD"
Ne, a už jsem to psal
"Nikdo Vás taky nenutí pouštět všechny testy úplně pokaždé, ale většinou (mrkněte do manuálu) se dají pustiti testy jenom pro nějakou jednu dvě složky, něco jako např.
tests/[func|unit]]/{*}HandlingService (pokud to teda nemáte všechno úplně na jedné hromadě) a nebo se dá často filtrovat podle názvu, např. něco jako
./run --filter "Users|Aeroplane"
či třeba jenom
./run tests/functional/services/someService::testHandleSomething což ale může zahrnovat i dalších x vrstev, y souborů a hromadu dat "
D) "To, že to všechno projde u Vás ještě vůbec nic neznamená"
Ano i ne. Není to nutná ani postačující podmínka, ale pro ulehčení si práce a zefektivnění procesu je to velmi užitečné. Ono totiž ani to že Vám projdou testy na CI/CD neznamená že jste otestoval všechno a neexistuje tam nějaká chyba která tam byla zanesena před tím nebo nějakou opravou. Pak jsou na řade quality testy ale tak co si do nich nenapíšete to tam nemáte a chybu lze udělat i v psaní testů - taky se mi stalo mnohokrát.
@Jakub Daňek
No to záleží co s danou databází provádíte. Každopádně pro lokální testování je velmi výhodné použít dobře cashovatelnou databázi. Pak samozřejmě v rámci buildu do dev (a dále) prostředí použijete "reálnou" verzi databáze. To se dá v konfiguraci i dockeru udělat hierarchií config files celkem jednodušše
IMHO proti skutečně reálné databázi (ta/ty skutečně reálné instance s se skutečnými uživatelskými daty) nebudete testovat nikdy. Vždy existuje nejaká alespoň minimální změna v konfiguraci nebo reálném stavu pro různá prostředí.
Nevím, co je to dobře „cashovatelná“ databáze, každopádně i takovému PostgreSQL lze spustut separátní instanci. Kdysi jsem to ubastlil bez Dockeru (předpokládalo to volný port a nainstalovanou vhodnou verzi PostgreSQL), s Dockerem to půjde ještě lépe.
Ano, pro účely integračních testů jsem tomu vypnul takové „zbytečnosti“ jako fsync [1], aby to bylo rychlejší. Pravda, takové nastavení se odlišovalo od produkce, kde je fsync důležité, ale za tu rychlost testů to stálo a i tak to poskytovalo mnohem věrnější výsledky než testování oproti SQLite.
Ono testování je vždy kompromis. Vždy chceme co nejvěrnější výsledky s co nejnižším úsilím a co nejrychlejšími testy. To někdy jde proti sobě. Mít na produkci PostgreSQL a testovat na SQLite znamená používat databázi s jinými specifiky, jinými tichými defaulty (včetně řazení) apod. V době Dockerové typicky není velký rozdíl mezi náročností rozjet SQLite a rozjet PostgreSQL, takže těžko se pro to hledá dobrý důvod. Ani ORM nemusí člověka 100% odstínit od rozdílů – na to by muselo srazit možnosti všech databází na úroveň té nejméně pokročilé.
Naproti tomu takový zakázaný fsync má celkem popsané dopady. Pokud nedojde k tvrdému vypnutí (které nás stejně nezajímá, když v takovém případě databázi zahodíme a vytvoříme novou a prázdnou), rozdíl by měl být pouze v rychlosti. To čistě teoreticky samozřejmě nějaký rozdíl může udělat (pak bychom ovšem ani nemohli použít debugger [2]), ale riziko vidím jako řádově nižší než u použití jiného RDBMS.
[1] https://www.postgresql.org/docs/9.5/non-durability.html
[2] https://en.m.wikipedia.org/wiki/Heisenbug
@Vít Šesták
Jej, to mi nedošlo ... měl jsem na mysli cache databáze aby se nemusela před každým testem obnovovat do výchozího stavu, ne cache dotazů apod.
My máme např. v local dev prostředí asi 9 Docker kontejnerů (komplet local dev env) - některé simulují služby dostupné v amazonu jako RDS (Databáze) nebo nějaký Cloud (to jsem nenastavoval já ani jednou a nikdy se s tím nemuselo nic dalšího řešit ...) a některé (hlavně aplikace) běží přímo v Amazon Elastibeanstalk jako kontejner aplikace nahrávané přes CI/CD. Pro testy na localhostu používáme SQLite, je to jeden soubor, běžně je dostupný v daném kontejneru tato DB je cacheovaná mezi testy. Jak jsem psal, je tam jenom jedno místo u kterého se musel udělat jedena podmínka kvůli nějaké funkcionalitě která se v SQLite chová jinak - je to sice zásah do aplikace "pouze" kvůli testům, zdálo by se to nepřípustné, na druhou stranu je to místo kde už je vyzkoušené že při změně typu databáze bude/byl nějaký problém :-) tedy funkce specifická pro aktuální technologii ...
Tady už se dostávám na tenký led, ale ještě jsem neviděl že by větší databáze běžely v kontejnerech, takže stejně se nějakému ladění defaultů nevyhnete a při každé změně na produkci musíte konfigurovat i vývojové prostředí. Nejlepší to určitě není, ale zatím jsem se fakt už ve více firmách setkal pouze s tím že databáze prostě běžela někde na vlastní pěst, často "Managed" nějakým dohledovým centrem jako služba. Je pravda, že když se to začne rozcházet moc, nejde každá obezlička použít ... DOckerové farmy jsem ještě nepoužíval, naposledy když jsem o tom četl tak tam byla větička o jakémsi experimentálním cosi a běhu v produkci na vlastní riziko .... nevím. nejsem machr přes databáze ...
@Jakub Daněk
Ano, nemusí tam fungovat všechno, v celé aplikaci máme jedno takové místečko (vyřešeno jednou podmínkou), ale pro lokální vývoj je to (minimálně v našem případě naprosto) dostačující. Celá aplikace je v Docker kontaineru Symfony+Doctrine, pro lokální vývoj je použitá SQLite která se mezi testy nemusí uvádět znova a znova do výchozího stavu, pak letí celý kontejner někde ke konci CI/CD procesu do produkce a po cestě už _všechny_ testy valí oproti reálné nedockerizované databázi.
My s tím právě měli problémy a ne vždy bylo na první pohled zřejmé, že problém vzniká rozdílnou DB. V době kdy není problém si v dockeru pustit libovolnou databázi na lokálu mi to přijde jako zbytečný odklon od produkčního prostředí.
Podobně s tím cachováním db mezi testy - podle mého to snižuje opakovatelnost testů, protože se obecně mění vstupní podmínky (testy se pravděpodobně pouští v náhodném pořadí, občas se nějaké testy mění nebo přidávají/ubírají). A já od testů očekávám, že ověřují, že se testovaná část systému chová po změně stejně jako předtím. A k tomu potřebuji kontrolované prostředí.
Výše píšete, že nikdy nemáme testovací prostředí úplně shodné s produkčním. To je sice pravda, ale není to pro mě argument pro to se od něj cíleně vzdalovat.
Abych to shrnul: chápu důvody proč se to používá, ale v dnešní době mi to přijde překonané.
@Jakub Daněk
Už jsem to psal výše, špatně jsem se vyjádřil. Cacheovaná je celá databáze - její výchozí stav, ne dotazy a kdoví co to umí ještě.
"V době kdy není problém si v dockeru pustit libovolnou databázi na lokálu mi to přijde jako zbytečný odklon od produkčního prostředí."
No, on to problém je. Pro vývoj si ji sice hezky spustíte, ale (abych byl konkrétní) v našem případě teď aktuálně trvá uvedení databáze do výchozího stavu asi 5 min. a ještě je v DEV prostředí navázaná na další - ale ty jsou obnovit rychleji. Těžko si lze představit takto spustit třeba 8 testových metod a před každou uvést databázi do výchozího stavu. Dost nepříjemné je když to musíte udělat 3x za sebou při manuálním testování. S použitím SQLite pod Doctrine je to v řádu vteřin.
"Výše píšete, že nikdy nemáme testovací prostředí úplně shodné s produkčním. To je sice pravda, ale není to pro mě argument pro to se od něj cíleně vzdalovat."
Neoznačoval bych použití jiné databáze pro účely spouštění lokálních testů za účelové vzdalování. Pro vývoj na localhostu máme samozřejmě plně dockerizované prostředí - několik DB instancí a script na jejich uvedení do výchozího stavu i s default daty. Tím se taky hned objeví problém v běhu testů pod jinouo DB. Nicméně pokud má být datový engine zaměnitelný, těžko pak argumentovat tím že je to problematické. Mohu Vám říct že v našem případě rozhodně není a aplikaci bych hodnotil jako nejméně středně složitou, možná i více.
"Abych to shrnul: chápu důvody proč se to používá, ale v dnešní době mi to přijde překonané."
Řekl bych že naopak. Díky modernímu přístupu a abstrakci se z toho dá velmi dobře těžit. Říkáte co je špatně, ale jak je to tedy správně? Máte nějaký konkrétní příklad na nějaké větší/složitější aplikaci?
Co je správně přece říkám: testovat proti stejné databázi. Uvádíte, že problém je dlouhá inicializační doba. Nevím, co to všechno obnáší, ale nešel by vytvořit docker image s už nainicializvanou databází? Vytvoření nového kontejneru z image je pak otázka pár vteřin.
Jinak mé komentáře k tématu směřovaly k obecnému případu, a k tomu co je principielně (imo) správně. Každá odchylka by pak měla být dobře podložená a udělaná s vědomím všech důsledků + zadokumentovaná. Což např. pro Vás očividně platí - databáze startující 5 minut je při vývoji samozřejmě k ničemu. Možné řešení viz výše, ale detaily znáte samozřejmě jen Vy.
Je to vždycky něco za něco, vývoj software se nedělá na krásu, ale na účelnost (tedy měl by). Pokud databáze startuje rychle, je dražší hledání podivnosti kvůli rozdílnému RDBMS. Pokud databáze startuje pomalu, vyjde levněji akceptovat fakt, že se v některých případech mohou chovat různé databáze jinak.
P.S: Už jsem si asi vzpomněl v čem byl tehdy problém: nebyly plně kompatibilní nějaké datové typy a museli jsme udržovat dvě sady DDL skriptů. Myslím, je to dávno :).
@Jakub Daňek
Nejde o to jak rychle startuje dazabáze, uvedení databáze do výchozího stavu (data) je časově nepřijemné - speciálně pro použití v testech. O tom je tu řeč.
"Nevím, co to všechno obnáší, ale nešel by vytvořit docker image s už nainicializvanou databází?"
Já měl za to že to máte pořešené a poučíte mne jak se to dělá správně.
Já ale chápu, že jde o dobu inicializaci dat.
A řešení jsem Vám navrhl: udělejte si docker image, který už obsahuje inicializovanou databázi.
Jestli je ve Vašem projektu něco, co takové použití znemožňuje nevím. Taky se Vám to asi nevyplatí předělávat, když máte stabilní odladěné prostředí. Pro nový projekt by mi to přišlo lepší.
@Jakub Daněk
Dobře, připustím že Docker dokonale substituje skutečné produkční prostředí - protože pokud databáze neběží někde jako úplně stejný Docker kontejner i na produkci, vždy je celkem šance že bude v něčem jiná. Připomenu že docker není 1:1 virtuál, ale má vytvořit možnost spustit stejný kontejner jak ve výjovém, tak produkčním prostředí.
Uvedl jste "Nevím, co to všechno obnáší, ale nešel by vytvořit docker image s už nainicializvanou databází?". Možná jsem to špatně pochopil, ale to mi nepřipadá jako "používám to a vím že je to lepší".
Takovou verzi s dockerem jsem používal pro "ručně" spouštěné testy s unit test docker conteinerm od maintainera a nějakým mým vlastním - budu ten projekt muset pohledat, ale pamatuji že tam byl nějaký dowside. A to ještě nevím jak se k tomu postaví buildové prostředí na serverech - zatímco tady mi stačí zkopírovat jeden soubor s daty a dokonce na takové testování existuje běžně používaná knihovna. Ale nasadil jste mi brouka do hlavy, to je pravda, musím se mrknout jestli se mi nepodaří s tím nějak pokročit, možná prokázat co je lepší.
30. 1. 2020, 17:48 editováno autorem komentáře
Mně přišlo, že jsme si úplně nerozuměli. Tohle řešení máme vyzkoušené a ulehčilo nám to hodně starostí spojených právě s udržováním testovací databáze. Konkrétně pro konfiguraci kontejnerů pro testy používáme: https://www.testcontainers.org/. Testy samotné neběží v kontejneru, ale na pracovní stanici/build serveru a jen si spouští kontejner s databázi.
Docker samozřejmě produkční prostředí plně nenahradí (pokud teda nemáte produkční databázi v dockeru, ale to patří spíš do kategorie těch horších nápadů). Myslím, že celá naše debata je vlastně o tom, jaké kompromisy jsme ochotni udělat a které nám přijdou lepší/horší :).
@Jakub Daněk
Děkuji za link, určitě se na to podívám, zdá se dokonce na letmý pohled že tam toho není moc :-)
No ona ta debata vlastně vznikla na zaměnitelnosti vrstev, takže to o čem se bavíme jsou teoreticky dvě strany jedné mince - bavili jsme se o zaměnitelnosti vrstev, ale jedna strana mince vlastně je že není dobré je zaměňovat (a testovat v idálně přesně odpovídajících kontejnerech) a druhá, ta o které mluvím já, je dbát na praktickou zaměnitelnost, testovat s použitím jiných databází (vlastně jenom kvůli rychlosti testů), ale pak nemít plně shodné prostředí s produkčním.
Ano, uvědomuji si že mluvím o vrstvách, zatímco reálně řeším/řešíme jenom datovou vrstvu - to ale vlastně protože nevěřím, z hlediska mých praktických zkušeností, na to, že když použitím nějaké technologie budujete nějakou třeba prezzentační vrstvu několik let, tak ji můžete nějak jen tak zaměnit výměnou nějakého enginu. To je utopie a krom databázového uložiště a systémů postavených na APIs jsem neviděl že by se někdy něco takového měnilo nebo se dalo vyměnit bez zahození a znovunapsání.
Aplikační vrstvy mi dávají vetší smysl z důvodu testování a možnosti řekněme substituce v rámci vrstvy, nebo spíš dekompozice v třídách, ale jinak asi prakticky ne ....
31. 1. 2020, 12:48 editováno autorem komentáře
Dobrý den,
V ideálním případě je cibulová architektura vhodná k výměně jedné z vrstev. V reálném případě si dokážu představit, že výměna si bude žádat nějaké úpravy, ovšem myslím si, že použitím této architektury se vám podaří minimalizovat nutný čas programátorů k výměně. Pokud může být programátor k tomu připraven, proč tomu nejít pomocí architektury naproti?
Nesouhlasím s Vámi v tom, když tvrdíte, že "pokud zaškrtnu tenhle checkbox, tak schovej tyhle dvě políčka" je bussiness rule. Něco takového je specifikace chování UI a nemá nic společného s tím, jak bussiness zákazníka funguje.
Cibulová architektura bude i umožňovat lehčí koexistenci dvou prezentačních vrstev, kterou zmiňujete. Jelikož je přepis celé prezentační vrstvy velmi náročný na finance a čas, tak by měla architektura aplikací podporovat možné scénáře vývoje co nejlépe.
SOLID principy jsou výborné principy a Cibulová architektura je podporuje na úrovni vrstev.
Souhlasím, že se tyto principy používájí obecně už dlouho a určitě se najdou vývojáři, kteří jen neví, že se tomu takhle říká.
K té výměně:
1) Pokud máte aplikační logiku ve vnějších vrstvách, tak si o problémy přímo říkáte a není to problém architektury ale kvality implementace. Souhlasím že je to, bohužel, běžná praxe.
2) Pokud chcete něco nahrazovat, musíte přesně znát kontrakt včetně vedlejších účinků. Pak jste schopni zaručit funkčnost i po nahrazení. V praxi to samozřejmě není úplně jednoduché, pomáhá kvalitní pokrytí testy.
3) A asi nejdůležitější pro myšlenku jako takovou: nelze hodnotit jak "snadné" je nahradit konkrétní implementaci vrstvy za jinou v absolutních číslech (protože do toho hodně mluvít velikost projektu, kde zrovna Vámi zmiňované systémy jsou peklo na údržbu tak nějak z principu), ale spíš v porovnání s tím, jak složité by to bylo, kdyby byla architektura navržena a naimplementována špatně.
Má osobní zkušenost je, že důsledné dodržování podobných pravidel vede k výrazně snazší a levnější údržby projektu včetně dalšího vývoje. Přičemž výměnou vrstvy je možno rozumět i třeba migrace na novější verzi technologie, což už není operace úplně neobvyklá.
Naopak, pokud je systém opravdu velký a implementace není dostatečně kvalitní, je lepší do toho moc nesahat, přesně jak píšete.