Architektonické vzory podnikových aplikací Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
1 z 124
Obsah 1. Úvod 2. Vrstvy 3. Organizace aplikační logiky 4. Obecné vzory 5. Vzory pro zdroje dat 6. KONEC
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
2 z 124
1. Úvod Obsah 1.1 Literatura 1.2 Co je to SW architektura 1.3 Vlastnosti podnikových aplikací 1.3.1 Výhody oproti technologickým aplikacím 1.3.2 Specifické problémy práce s daty 1.3.3 Problémy s implementovanými algoritmy 1.4 Úvahy o výkonnosti 1.4.1 Druhy optimalizací – terminologie
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
3 z 124
1.1 Literatura ►
MCLAUGHLIN, Brett, POLLICE, Gary a WEST, David. Head first object-oriented analysis and design. 1st ed. Sebastopol: O'Reilly, ©2006. xxxiv, 600 s. ISBN 0-596-00867-8.
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
4 z 124
►
BOOCH, Grady et al. Object-oriented analysis and design with applications. 3rd ed. Upper Saddle River: AddisonWesley, 2007. xxiii, 691 s. The Addison-Wesley object technology series. ISBN 978-0-201-89551-3.
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
5 z 124
►
GOMAA, Hassan Designing Software Product Lines with UML: From Use Cases to Pattern-Based Software Architectures. Addison-Wesley Professional, 2004. 736 s. . The Addison-Wesley object technology series. ISBN: 978-0-201-77595-2
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
6 z 124
►
GORTON Ian Essential Software Architecture. Springer 2011. 258 s. ISBN: 978-3-6421-9175-6
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
7 z 124
►
FOWLER, Martin et al. Patterns of enterprise application architecture. Boston: Addison-Wesley, ©2003. xxiv, 533 s. The Addison-Wesley signature series. ISBN 0-321-12742-0.
►
Z této publikace vychází následující výklad a je z ni převzata i většina obrázků
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
8 z 124
1.2 Co je to SW architektura ►
Existuje záplava definic, některé jsou přesné, jiné výstižné
►
Laikův pohled: Architektura je slovo, které programátoři používají, když chtějí naznačit, že hovoří a něčem důležitém
►
Programátorův pohled: Architektura je subjektivní pohled na systém, na němž se shodli experti pracující na projektu ● Popisuje podobu a komunikaci hlavních komponent ● Ovlivňuje řadu detailů, takže je třeba ji mít navrženou co nejdříve,
abychom nemuseli své programy později zásadně měnit
►
Pohled manažera: Architektura projektu popisuje to, co se na projektu nemění ● Výstižnější by bylo: co by se na projektu měnit nemělo,
protože zásahy do architektury patří k těm nejdražším ● Začneme-li v průběhu vývoje měnit architekturu systému, koledujeme si o problémy (čím později, tím větší) Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
9 z 124
1.3 Vlastnosti podnikových aplikací ►
Podnikové aplikace (enterprise applications) mají svá specifika
►
Často bývá potřeba je propojit s řadou jiných aplikací ● Mnohé z nich přitom pracují nad stejnými daty,
přestože jsou postaveny na zcela odlišných platformách ● Mnohde má nová aplikace povinnost spolupracovat s nějakou léta používanou aplikací vytvořenou na jiné platformě – např. s prehistorickou aplikací napsanou v Cobolu ►
Občas se v nich musejí realizovat postupy, které pro autora programu zdánlivě nemají žádnou logiku
►
Bývají určeny pro laické uživatele přesvědčené o vlastní důležitosti
►
Zákazník občas vyžaduje v průběhu vývoje zapracování změn, které zasahují až do architektury systému
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
10 z 124
1.3.1 Výhody oproti technologickým aplikacím ►
Nejsou příliš závislé na konkrétním HW a nemusí se mu proto bezpodmínečně přizpůsobovat jak tomu bývá u technologických aplikací
►
Neprovádějí náročné vědecké výpočty ani rozsáhlé simulace, takže nepotřebují supervýkonné procesory
►
Nemusí řešit problémy vyžadující synchronizaci mnoha nezávisle pracujících vláken jako např. v telekomunikacích
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
11 z 124
1.3.2 Specifické problémy práce s daty ►
Zpracovávají rozsáhlá a komplexní data ● Středně velký systém mívá GB dat organizovaných do 107 záznamů ● Zpracování dat a organizace databází vyžaduje vlastní specializaci
►
Musejí zabezpečit perzistentnost (trvalost) zpracovávaných dat ● Často po dobu mnoha let ● Nezávisle na případných změnách obslužného hardwaru i softwaru
(mění se nejen zpracovávající program, ale i operační systém & spol.)
►
Musejí zabezpečit korektní paralelní přístup k datům pro mnoho uživatelů ● Uživatelé přitom mívají malé technické znalosti a zkušenosti,
takže programy musí být obrněny proti nestandardnímu ovládání
►
Vyžadují definici spousty různých uživatelských rozhraní ● Řada aplikací používá stovky různých obrazovek,
které musejí být přizpůsoben úrovni znalostí a zkušeností uživatelů
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
12 z 124
1.3.3 Problémy s implementovanými algoritmy ►
Pravidla, jimiž se řídí jednotlivá oddělení, si často odporují, a přitom chce mít každé oddělení systém ušitý na míru ● jedno oddělení pracuje pouze se zákazníky s aktuální smlouvou, ● druhé přibírá i zákazníky, s nimiž byla někdy uzavřena smlouva, ● třetí nezapočítává do ceny výrobku cenu následných služeb ● atd. atd.
►
Pracuje-li systém s miliony záznamů, jejichž jednotlivá pole mají v různých kontextech různý význam, bývá obtížné vše zharmonizovat
►
Řada pravidel, jimiž se musí jejich zpracování řídit, je odvozena od konkrétních dohod s konkrétními zákazníky ● Obchodník se např. dohodne se zákazníkem, že smí platit o týden později,
protože to vyhovuje zákazníkovu účetnímu systému; sice tím získá lukrativní zakázku, ale nabourá práci systému, po němž chce, aby se jeho požadavkům přizpůsobil
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
13 z 124
1.4 Úvahy o výkonnosti ►
Řada architektonických rozhodnutí se týká výkonu aplikace, přičemž u mnohých z nich není vůbec jasné, jak dané rozhodnutí ovlivní výsledný výkon aplikace
►
Nezávisle na předvídatelnosti výsledného efektu, většina těchto úvah vstupuje do hry příliš brzy
►
O výkonu nemá smysl se dohadovat do okamžiku, kdy jej můžeme začít měřit
►
V řadě případů byla věnována zbytečná pozornost optimalizaci, která se při následném měření ukázala zbytečná, a naopak byly zanedbány činnosti, jejichž vliv na celkový výkon se ukázal jako klíčový
►
Při každé snaze o optimalizaci je třeba měřit výkon před provedením změny a pak znovu po ní, aby se neukázalo, že se touto „optimalizací“ kód zkomplikoval, ale bez vlivu na výkon
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
14 z 124
1.4.1 Druhy optimalizací – terminologie ►
Doba odezvy (Response time) je čas, který systém potřebuje ke zpracování vnější události, např. stisku tlačítka či volání API
►
Vstřícnost (Responsiveness) je míra toho, jak rychle systém potvrdí přijetí požadavku, v poměru k době jeho zpracování ● Systémy s malou vstřícností řadu uživatelů frustrují,
i když je doba jejich odezvy krátká ● Systém by proto měl přijetí požadavku oznámit co nejdříve, byť jej bude ještě nějakou chvíli zpracovávat ● Malá vstřícnost systému vede řadu uživatelů k opakovanému zadání požadavku, čímž zbytečně zahlcují systém ● Vstřícnost je možné zlepšit zobrazením ukazatele průběhu (progress bar)
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
15 z 124
►
Latence (Latency) označuje minimální čas potřebný k získání nějaké odpovědi, i kdybychom chtěli něco, co systém nedělá ● Latenci programu lze měřit žádostí o provedení prázdné či neexistující akce
Požádáte-li o provedení prázdné akce program na svém počítači, okamžitě odpoví, že nebude dělat nic ● Žádáte-li o totéž program na vzdáleném úložišti, můžete trvat i několik sekund, než se něco dozvíte ●
►
Průchodnost (Throughput) Kolik požadavků je systém schopen zpracovat za jednotku času ● Průchodnost se typicky udává v počtu transakcí za sekundu,
ale v některých případech je výhodnější jiná míra ● Absolutní číslo toho moc neříká, dokud nevíte, jak složitá je jedna transakce ►
Výkon, výkonnost (Performance) ● Udává se jako průchodnost nebo doba odezvy, občas jdou ale proti sobě ● Z hlediska zákazníka je důležitější doba odezvy,
tvůrci systému však často sledují především průchodnost
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
16 z 124
►
Zátěž, vytížení (Load) označuje aktuální stav systému ● Většinou se udává počet aktuálně připojených uživatelů ● Na aktuální zátěži závisí většina ostatních parametrů;
lze pak např. udávat, že doba odezvy je 0,5 sec pro 10 uživatelů a 2 sec pro 20 uživatelů
►
Citlivost na zátěž (Load sensitivity) udává, jak se mění doba odezvy v závislosti na aktuální zátěži ● Někdy se používá termín degradace –
citlivější systém více degraduje při růstu aktuální zátěže
►
Účinnost (Efficiency) bývá udávána jako výkon dělený množstvím použitých zdrojů ● Systém schopný zpracovat 30 trans/s na dvou CPU je účinnější než
systém schopný zpracovat 40 trans/s na čtyřech CPU ● Účinnost systému je mírou ceny jedné transakce ►
Kapacita (Capacity) bývá udávána jako maximální efektivní účinnost či zátěž
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
17 z 124
►
Škálovatelnost (Scalability) je míra toho, nakolik jsou přidané prostředky schopny ovlivnit výkon aplikace ● Škálovatelný systém je schopný zvyšovat výkon úměrně přidaným zdrojům
►
Horizontální škálovatelnost (Horizontal scalability, scaling out) charakterizuje změnu výkonu v závislosti na počtu použitých serverů (a s nimi i virtuálních strojů) ● Na každém serveru je program spuštěn na vlastním virtuálním stroji
►
Vertikální škálovatelnost (Vertical scalability, scaling up) charakterizuje změnu výkonu systému v závislosti na výkonu daného stroje (množství paměti, množství jader, …) ● Prostřednictvím virtualizace můžeme transformovat
horizontální škálování na vertikální
►
Rozšiřitelnost (Extensibility) nesouvisí přímo s výkonností systému a označuje schopnost systému akceptovat přidání další funkcionality
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
18 z 124
2. Vrstvy Obsah 2.1 Úvod 2.1.1 Výhody rozdělení systému do vrstev 2.1.2 Nevýhody vrstev 2.2 Vývoj vrstev v podnikových aplikacích 2.3 Třívrstvá architektura 2.3.1 Vazby mezi vrstvami 2.3.2 Hranice mezi vrstvami 2.3.3 Rozhodování o umístění vrstev 2.3.4 Kam umístit jednotlivé vrstvy
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
19 z 124
2.1 Úvod ►
Rozkládání systému do vrstev je jednou z nejběžnějších programátorských technik ● Program Operační systém Ovladače Instrukční soubor P Hradla ● Aplikace FTP TCP IP Ethernet HW
►
Každá vrstva využívá služby definované nižší vrstvou a poskytuje služby pro vrstvu vyšší
►
Každá vrstva definuje rozhraní, které může používat vyšší vrstva
►
Vhodně definované rozhraní umožňuje vyměnit jednu vrstvu, aniž bychom museli vyměňovat ty ostatní
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
20 z 124
►
Vrstvy většinou skrývají jimi využívané nižší vrstvy před vyššími vrstvami využívajících jejich služeb
►
Architektura může být vrstvena silně nebo slabě
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
21 z 124
2.1.1 Výhody rozdělení systému do vrstev ►
Jednotlivou vrstvy lze snáze pochopit, i když nevíme skoro nic o nižších vrstvách, které tato vrstva využívá ● K tomu, abyste pochopili spolupráci vašeho programu
s třídami virtuálního stroje, nemusíte vědět skoro nic o komunikaci virtuálního stroje s operačním systémem ● K tomu, abyste byli schopni vybudovat službu FTP nad protokolem TCP, nemusíte vědět nic o principech práce ethernetu ►
Je-li architektura vrstvená silně, můžeme vrstvu snadno nahradit jinou implementací stejných služeb ● Program v Javě může běžet nad různými operačními systémy ● Webový program běhá na různých prohlížečích ● Při přechodu na jinou technologii sítě stačí vyměnit síťovou kartu
►
Lze to i se slabě vrstvenými architekturami, ale dá to výrazně víc práce
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
22 z 124
►
Vrstvy jsou výhodným řešením pro standardizaci ● Bohužel, v řadě případů chtějí výrobci zdůraznit dokonalost svých výrobků
přidáním nějakých rozšíření oproti standardu, což ale v důsledku vede k problémům s kompatibilitou ● Typické příklady: Java ME, Android ►
Jakmile je vrstva vytvořena, lze ji použít pro celou plejádu služeb vyšší úrovně
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
23 z 124
2.1.2 Nevýhody vrstev ►
Vrstvy leccos zapouzdří, ale nemohou zapouzdřit vše; v důsledku toho je občas třeba provést celou kaskádu změn ● Příklad: když při vrstvené architektuře databázové aplikace
potřebujete přidat do databáze (nejnižší vrstva) nějaké pole, jehož obsah se má zobrazit v UI (nejvyšší vrstva)
►
Přidávání vrstev snižuje výkon ● Každá vrstva většinou převádí data z jedné reprezentace do jiné ● Zapouzdření funkcí nižší úrovně však může někdy přinést zisk,
který tuto ztrátu bohatě vynahradí ● Vrstva kontrolující transakce může být optimalizována, čímž se může výrazně zrychlit činnost ● Překladače, správci paměti, …
►
Nejtěžší je rozhodnout, jaké vrstvy vytvořit a jaká bud funkce a zodpovědnost každé z nichz
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
24 z 124
2.2 Vývoj vrstev v podnikových aplikacích ►
U starších programů se o vrstvách nehovořilo, tento termín se začal objevovat až na přelomu 80. a 90. let s nástupem architektury klient-server
►
Architektura klient-server je dvojvrstvá ● Server většinou zprostředkovává přístup k relační databázi ● Klient zpracovává obdržená data a komunikuje s uživatelem
►
Nevýhody této architektury: ● Aplikační logika, validace vstupů a potřebné výpočty
byly prováděny na klientovi ● Jak se logika postupně zesložiťovala, bylo stále obtížnější ji na klientech udržovat ►
Jisté řešení přinášely uložené procedury, které přenášely část logiky na správce databáze (SŘDB – DBMS)
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
25 z 124
►
Paralelně se začalo prosazovat OOP, které se pokusilo vyřešit tyto problémy zavedením třívrstvé architektury: ● Prezentační vrstva byla zodpovědná za komunikaci s uživatelem (UI) ● Aplikační (doménová) vrstva měla na starosti aplikační (doménovou) logiku ● Datová vrstva obhospodařovala databázi
►
Třívrstvá architektura se prosazovala jen pomalu, protože přechod nebyl jednoduchý a nepřinášela výhody úměrné pracnosti realizace potřebného přechodu
►
Změna názoru přišla s nástupem webu ● Uživatelé chtěli přistupovat k aplikaci přes webový prohlížeč,
což se neslučovalo s uložením aplikační logiky u klienta
►
Přechod ovlivnil i masový nástup jazyka Java ● Objektově orientované programování se stalo hlavní technologií ● Začaly se objevovat nástroje, které byly více objektové
a méně ovlivněné jazykem SQL ● Další urychlení přineslo zavedení standardů a frameworků typu J2EE ● To vše ovlivnilo, že se třívrstvá architektura prosadila Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
26 z 124
2.3 Třívrstvá architektura ►
Prezentační vrstva je zodpovědná za komunikaci s uživatelem, tj. za předání informací a převzetí požadavků ● Příkazový řádek umožňující ovládání i z jiných programů;
uživatele tak může zastupovat onen program ● Grafické uživatelské rozhraní tlustého klienta ● Webový klient ►
Datová vrstva má na starosti komunikaci s ostatními systémy, které jménem dané aplikace žádá o řešení pomocných úkolů ● Hlavní součástí této vrstvy bývá SŘDB (DBMS)
zodpovědný za ukládání a opětné poskytování persistentních dat ● Patří sem ale i správce transakcí, systém zpráv a další podpůrné aplikace ►
Aplikační (doménová) vrstva má na starosti vlastní zpracování dat
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
27 z 124
2.3.1 Vazby mezi vrstvami ►
Aplikační vrstva často zcela zakrývá datovou vrstvu před vrstvou prezentační, ale není to nezbytně nutné; v řadě případů je výhodné, pokud si prezentační vrstva může přímo sáhnout pro potřebná data
►
Nižší vrstva by však neměla nikdy záviset na vyšší ● Vyšší vrstvy podléhají častějším změnám,
a není vhodné, aby se kvůli tomu musely ty nižší přizpůsobovat
►
Některé aplikace jsou v každé vrstvě tvořeny několika moduly ● Prezentační vrstva může mít modul pro přímou komunikaci s klientem,
a další pro komunikaci prostřednictvím příkazového řádku nebo dokonce publikovaného API ● Datová vrstva může zprostředkovávat komunikaci s několika zdroji dat ● Obdobně může být do více modulů rozdělena i aplikační vrstva ►
Naopak u některých velmi jednoduchých aplikací mohou být všechny tři vrstvy součástí jedné třídy a jednotlivé vrstvy pak mou být reprezentovány metodami
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
28 z 124
2.3.2 Hranice mezi vrstvami ►
Řada programátorů se obtížně rozhoduje o tom, co vše patří do doménové (aplikační) vrstvy a co do těch ostatních, zejména pak prezentační
►
Chybné prosakování aplikační a prezentační vrstvy lze odhadnou na základě jednoduchého testu: ● Definujte naprosto odlišné uživatelské rozhraní
(např. k webovému rozhraní přidejte druhé ovládané přes příkazový řádek) ● Podívejte se, jaké části programu to ovlivní a musíte je duplikovat – ovlivněné částí pravděpodobně patří do prezentační vrstvy ►
Obdobně programátoři občas zanášejí aplikační logiku do prezentační vrstvy ● Příklad: Zobrazte červeně produkty,
jejichž prodej stoupl oproti minulému měsíci o více než 10 % ● Výpočty pro předchozí příklad by se neměly dělat v prezentační vrstvě, ale ta by o ně měla požádat vrstvu aplikační Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
29 z 124
2.3.3 Rozhodování o umístění vrstev ►
Vrstvy, o nichž hovoříme, jsou logické, tj. není explicitně řečeno, zda poběží na tomtéž počítači, nebo každá na svém, anebo se prokombinují ještě jinak
►
Pravidla, podle nichž se rozhodujeme o místění jednotlivých vrstev na počítačích nemusí být jednoznačná
►
Nejčastěji se umisťuje vše na server ● Vše se pak snadno aktualizuje (upgraduje) ● Není třeba řešit problémy s instalací na různě konfigurovaných počítačích ● Nemusíte řešit problémy kompatibility s ostatními spuštěnými aplikacemi ● Pomocí webového prohlížeče se vše poměrně snadno řeší
►
Občas je ale vhodné umístit leccos na klientském počítači ● Odpadají problémy s přerušovaným spojením ● Při různých experimentech se snižuje síťový provoz a zátěž severu ● Někteří uživatelé chtějí mít aplikaci dostupnou všude
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
30 z 124
2.3.4 Kam umístit jednotlivé vrstvy ►
Datová vrstva bývá většinou na serveru ● Je li požadována dostupnost všude, musejí se data duplikovat ● V případě změny dat je třeba zajistit následnou synchronizaci
►
Umístění prezentační vrstvy závisí na použitém rozhraní ● U tenkého webového klienta běží i prezentační vrstva na serveru ● U tlustého klienta bývá prezentační vrstva na počítači uživatele
►
U některých aplikací (např. B2C) si nemůžeme vybírat a vše musí běžet na serveru ● V řadě případů pak ale musíme zabezpečit dostatečný tok dat ● Řešením bývá použít JavaScript
a naprogramovat v něm některé operace na klientu ● Druhou možností je využít apletů ● Obě možnosti ale přinášejí problémy s kompatibilitou (a nejen s ní) ● Nepomáhá ani sjednocení HW a SW v rámci firmy, protože uživatelé jsou tvůrčí a vynalézaví Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
31 z 124
►
Pokud je třeba umístit část logiky na uživatelský počítač, bývá vhodné uvažovat o možnosti umístit ji tam celou, protože se tím leccos zjednoduší
►
Rozdělení aplikační logiky mezi klienta a server přináší značné problémy s rozhodováním, co kde běží ● Většinou se toto řešení volí v případech, kdy na klientském počítači
§
musí běžet pouze malá část celkové logiky ● V takovém případě je vhodné izolovat tuto část do samostatného modulu, který bude maximálně nezávislý na ostatních částech systému – pak si můžete relativně svobodně vybrat, kde bude běžet
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
32 z 124
3. Organizace aplikační logiky Obsah 3.1 Úvod 3.1.1 Grafické srovnání vhodnosti vzorů 3.2 Příklad: Odhad výnosu 3.2.1 Vlastní příklad 3.3 Transakční skript (Transaction Script) 3.3.1 Princip 3.3.2 Sekvenční diagram transakce 3.3.3 Umístění skriptů 3.3.4 Uspořádání skriptů do tříd 3.3.5 Diagram tříd 3.3.6 Problémy s rostoucí složitostí 3.3.7 Řešení příkladu 3.3.8 Výhody a nevýhody transakčního skritpu 3.4 Doménový model (Domain Model)
3.4.1 Příklad: Odhad příjmu z konkrétní smlouvy 3.4.2 Principy 3.4.3 Příklad 3.4.4 Příklad: Přidáme strategii 3.4.5 Porovnání obou přístupů k řešení 3.4.6 Výhody a nevýhody doménového modelu 3.5 Tabulkový modul (Table Module) 3.5.1 Princip 3.5.2 Realizace 3.5.3 Příklad 3.6 Výběr mezi vzory 3.6.1 Ještě jednou grafické srovnání 3.7 Servisní vrstva 3.7.1 Doporučení:
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
33 z 124
3.1 Úvod ►
Organizace doménové logiky řeší tři základní architektonické vzory: ● Transakční skript (Transaction Script) ● Doménový model (Domain Model) ● Tabulkový modul (Table Module)
►
Nejjednodušší z nich je transakční skript, jenže jeho jednoduchost komplikuje řešení složitějších případů
►
Pro složité případy je výhodnější použít doménový model
►
Tabulkový modul je jistý kompromis obou předchozích řešení
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
34 z 124
3.1.1 Grafické srovnání vhodnosti vzorů
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
35 z 124
3.2 Příklad: Odhad výnosu ►
Běžný problém v obchodních aplikacích; řeší problém, kdy lze započítat peníze do účetnictví
►
Při standardním prodeji je lze započíst v okamžiku prodeje, ale v řadě případů je koncepce složitější
►
Obdržíte-li vratnou zálohu za svoji dostupnost v daném období, nemůžete ji započíst hned ● Jedním z řešení je započítávat měsíčně pouze odpovídající část,
abyste mohli zbytek bez problému vrátit v případě, že by kterákoliv strana od dohody ustoupila
►
Obdobných pravidel je řada a navíc se mohou měnit ● Pravidla mohou být nastavena zákonem,
firemní politikou nebo pouhou zkušeností
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
36 z 124
3.2.1 Vlastní příklad ►
Máme firmu, která prodává tři druhy výrobků ● Textové procesory ● Tabulkové kalkulátory ● Databáze
►
Pravidla pro odhad výnosů jsou následující: ● Při prodeji textového procesoru můžeme započítat tržbu ihned ● Při prodeji tabulkového kalkulátoru musíte započítat:
1/3 ihned, ● 1/3 po 60 dnech a ● 1/3 po 90 dnech ● Při prodeji databáze musíte započítat: ● 1/3 ihned, ● 1/3 po 30 dnech a ● 1/3 po 60 dnech ●
►
Nehledejte v tom logiku, v praxi tam často také žádná není
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
37 z 124
3.3 Transakční skript (Transaction Script) ►
Organizuje aplikační logiku tak, že každý typ požadavku je zpracováván samostatnou procedurou
►
Na většinu podnikových aplikací můžeme nahlížet jako na sérii transakcí ● Některé transakce uložené informace pouze analyzují, jiné je mění
►
Každá interakce mezi klientem a serverem vyžaduje jistou logiku; transakční skript organizuje celou tuto logiku do jediné procedury
►
Transakční skripty se na databázi obracejí v podstatě přímo pouze přes jednoduchý ovladač
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
38 z 124
3.3.1 Princip ►
Typicky je pro každou akci, kterou by mohl uživatel chtít vykonat, vyhrazena jedna procedura ● Jednotlivé procedury mohou samozřejmě sdílet společné podprogramy
►
Transakční skript je v podstatě procedura, která: ● získá vstup od prezentace, ● zvaliduje vstupní data, ● v případě potřeby požádá databázi o potřebná data, ● vše požadovaným způsobem zpracuje, ● je-li třeba, uloží upravená data do databáze, ● v případě potřeby vyvolá činnosti dalších systémů ● a vrátí zpracovaná data prezentační vrstvě (požadovaný výstup)
►
Dokud je logika jednoduchá, lze ji i jednoduše naprogramovat ● Ve složitějších případech je vhodné vše uspořádat do tříd a modulů
►
Výhodou je, že o sobě jednotlivé skripty nemusejí vůbec nic vědět
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
39 z 124
3.3.2 Sekvenční diagram transakce
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
40 z 124
3.3.3 Umístění skriptů ►
Umístění transakčních skriptů závisí na organizaci vrstev ● Mohou být na stránce serveru, v CGI skriptech
nebo v distribuovaných objektech pro jednotlivé session
►
Každý skript by měl mít alespoň vlastní metodu umístěnou navíc ve třídě, která se zabývá pouze aplikační logikou a vůbec se nezabývá ani prezentací, ani daty ● Jinými slovy není vhodné se až v průběhu vykonávání metody rozhodovat,
jaký transakční skript se má provádět
►
Transakční skripty by navíc neměly volat metody prezentační logiky ● Aplikace obecné zásady, že spodní vrstva nemá volat horní ● Aplikace se bude mnohem snadněji testovat a následně upravovat
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
41 z 124
3.3.4 Uspořádání skriptů do tříd ►
Nejčastější řešení: několik skriptů v jedné třídě ● Definujeme jednotlivé oblasti práce skriptů,
a pro každou oblast definujeme příslušnou třídu, přičemž jedna třída může obsahovat všechny skripty z dané oblasti ● U přiměřeně jednoduchých aplikací je to přehledné ►
V některých jazycích můžeme třídy zcela ignorovat a definovat skripty jako globální procedury, pak ale přijdeme o jejich výhody při práci s vlákny a izolaci dat
►
Méně časté: každý skript je definován ve vlastní třídě s použitím návrhového vzoru Příkaz (Command) ● Všem třídám pak navrhneme společného rodiče definujícího,
co po jednotlivých skriptech (objektech) můžeme chtít ● Výhodou tohoto řešení je, že s jednotlivými skripty můžeme nakládat jako s objekty ● Na druhou stranu v jednoduchých aplikacích, pro něž jsou TS vhodné, tuto možnost většinou nevyužijeme Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
42 z 124
3.3.5 Diagram tříd
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
43 z 124
3.3.6 Problémy s rostoucí složitostí ►
Hlavní výhodou transakčních skriptů je jejich jednoduchost; organizace logiky jednoduchých aplikací prostřednictvím TS je proto přehledná a všeobecně pochopitelná
►
Jakmile se aplikace stane složitější, vznikají problémy se špatně detekovatelnými duplikacemi aplikační logiky ● Protože programátoři tíhnou k vyřešení všeho v jednom TS,
je vznik duplicitního kódu poměrně běžný
►
Pokud rozměr aplikace dále narůstá, problémy rostou, ale přechod k jinému vzoru je náročný, takže bývá program stále „zdokonalován postaru“, přestože v součtu by byl přechod na jiný vzor levnější ● Při snahách o tento přechod se projevuje ekvivalent známého rčení:
„Proč řežete tupou pilou?“ „Nemáme čas ji nabrousit!“ ● Čím později se rozhodnete pro přechod, tím to bude dražší ►
Proto bývá velmi důležitý počáteční odhad výsledné složitosti
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
44 z 124
3.3.7 Řešení příkladu ►
Ukázka bude řešit dva transakční skripty: ● Spočtení odhadu výnosu pro daný kontrakt
jako součtu odhadů výnosů jednotlivých položek ● Odhad výnosu k zadanému datu ►
Databáze bude obsahovat tři tabulky: CREATE TABLE products (ID int primary key, name varchar, type varchar) CREATE TABLE contracts (ID int primary key, product int, revenue decimal, dateSigned date) CREATE TABLE revenueRecognitions (contract int, amount decimal, recognizedOn date, PRIMARY KEY (contract, recognizedOn))
►
Přístup k databázi zabezpečuje tabulková datová brána (Table Data Gateway)
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
45 z 124
►
První skript získá výnos k danému datu, a to ve dvou fázích: ● nejprve vybere vhodné řádky v tabulce příjmů, ● poté vše spočte
class Gateway... private Connection db; private static final String findRecognitionsStatement = "SELECT amount " + "FROM revenueRecognitions " + "WHERE contract = ? AND recognizedOn <= ?"; public ResultSet findRecognitionsFor(long contractID, MfDate asof) throws SQLException { PreparedStatement stmt = db.prepareStatement(findRecognitionsStatement); stmt = db.prepareStatement(findRecognitionsStatement); stmt.setLong(1, contractID); stmt.setDate(2, asof.toSqlDate()); ResultSet result = stmt.executeQuery(); return result; } Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
46 z 124
class RecognitionService... public Money recognizedRevenue(long contractNumber, MfDate asOf) { Money result = Money.dollars(0); try { ResultSet rs = db.findRecognitionsFor(contractNumber, asOf); while (rs.next()) { result = result.add(Money.dollars(rs.getBigDecimal("amount"))); } return result; } catch (SQLException e) { throw new ApplicationException (e); } } ►
Je-li výpočet dostatečně jednoduchý, může jej provést i příkaz SQL
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
47 z 124
►
Druhý skript počítá výnos zadaného kontraktu (viz zadání)
class RecognitionService... public void calculateRevenueRecognitions(long contractNumber) { try { ResultSet contracts = db.findContract(contractNumber); contracts.next(); Money totalRevenue = Money.dollars(contracts.getBigDecimal("revenue")); MfDate recognitionDate = new MfDate(contracts.getDate("dateSigned")); String type = contracts.getString("type"); if (type.equals("S")){ Money[] allocation = totalRevenue.allocate(3); db.insertRecognition (contractNumber, allocation[0], recognitionDate); db.insertRecognition (contractNumber, allocation[1], recognitionDate.addDays(60)); db.insertRecognition (contractNumber, allocation[2], recognitionDate.addDays(90)); } Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
48 z 124
else if (type.equals("W")){ db.insertRecognition (contractNumber, totalRevenue, recognitionDate); } else if (type.equals("D")) { Money[] allocation = totalRevenue.allocate(3); db.insertRecognition (contractNumber, allocation[0], recognitionDate); db.insertRecognition (contractNumber, allocation[1], recognitionDate.addDays(30)); db.insertRecognition (contractNumber, allocation[2], recognitionDate.addDays(60)); } } catch (SQLException e) { throw new ApplicationException (e); } }
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
49 z 124
►
Příkaz pro nalezení kontraktu
class Gateway... private static final String findContractStatement = "SELECT * " + "FROM contracts c, products p " + "WHERE ID = ? AND c.product = p.ID"; public ResultSet findContract (long contractID) throws SQLException { PreparedStatement stmt = db.prepareStatement(findContractStatement); stmt.setLong(1, contractID); ResultSet result = stmt.executeQuery(); return result; }
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
50 z 124
►
Příkaz pro uložení výsledku do databáze
class Gateway... private static final String insertRecognitionStatement = "INSERT INTO revenueRecognitions VALUES (?, ?, ?)"; public void insertRecognition (long contractID, Money amount, MfDate asof) throws SQLException { PreparedStatement stmt = db.prepareStatement(insertRecognitionStatement); stmt.setLong(1, contractID); stmt.setBigDecimal(2, amount.amount()); stmt.setDate(3, asof.toSqlDate()); stmt.executeUpdate(); }
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
51 z 124
3.3.8 Výhody a nevýhody transakčního skritpu ►
Výhody ● Je jednoduše procedurální, takže jej většina vývojářů chápe ● Dobře spolupracuje s jednoduchou datovou vrstvou používající
vzor Brána datového záznamu (Row Data Gateway) nebo vzor Brána datové tabulky (Table Data Gateway) ● Snadno se v něm nastavují hranice transakcí: procedura začne otevřením transakce a skončí jejím zavřením ►
Nevýhody ● Nevýhody se začnou projevovat s růstem složitosti
implementované aplikační (doménové) logiky ● Objevuje se v něm řada duplicit, které je sice možno refaktorovat, ale při jejich množství je obtížné je všechny odhalit a následně je korektně „vytknout“ ● Po výše zmíněných úpravách vzniká složitý, vnitřně provázaný kód bez zjevné přehledné struktury ● Při složitější aplikační logice je proto výhodnější dát přednost vzoru Doménový model Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
52 z 124
3.4 Doménový model (Domain Model) ►
Doménový model je objektový model řešené domény, který zahrnuje data i chování
►
Tento vzor doporučuje nejprve navrhnout model aplikační domény, který je (alespoň v první aproximaci) organizován kolem podstatných jmen popisujících klíčové entity
►
V tomto modelu pak bude veškerá logika distribuována mezi jeho objekty; každý objekt zodpovídá za svoji část logiky
►
Příklad: ● Systém spravující aktivity leasingové firmy bude pracovat s objekty
Smlouva, Aktiva, Dodávka, Poplatek apod. ● Objekt Dodávka tak bude obsahovat např. logiku pro spočtení doručovacích poplatků
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
53 z 124
3.4.1 Příklad: Odhad příjmu z konkrétní smlouvy
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
54 z 124
3.4.2 Principy ►
Vytvoření doménového modelu vede k vytvoření celé vrstvy objektů, které modelují problémovou oblast ● Objekty reprezentují jak vlastní data, tak i pravidla ● Každý objekt by se měl umět postarat o vše, co s ním přímo souvisí
►
Objektový model vypadá často podobně jako databázový, nicméně je mezi nimi řada rozdílů – doménový model: ● Sdružuje data a procesy ● Používá strukturované atributy ● Definuje komplexní síť vazeb ● Používá dědičnost
►
Používají se dva typy modelů ● Jednoduchý je blíže databázovému, objekty odpovídají tabulkám ● Komplexní zavádí řadu dalších objektů a vazeb modelujících logiku,
problematičtěji se však mapuje na jednotlivé tabulky
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
55 z 124
►
Model by měl být připraven na možné změny modelovaných pravidel
►
Zavedení všech objektů do paměti bývá náročné jak na paměť, tak na čas => ● Řada z nich zůstává většinu času v databázi ● Bývá výhodné používat objektovou databázi ● Jako náhražka se používá ORM, ale to vyžaduje svoji režii
►
Problémem občas bývá zbytečná složitost modelu ● Je třeba zabezpečit, aby se každý objekt staral jen o to,
za co je zodpovědný ● U každé funkce je třeba si ujasnit, zda opravdu patří do dané vrstvy, a pokud ano, zda je daný objekt opravdu ten, kdo to má řešit ►
Při úvahách nad funkcemi je třeba rozlišovat ● obecné funkce týkající se celé skupiny objektů (tříd) ● specifické pro daný objekt (třídu)
►
Při návrhu specifických funkcí vznikají často duplicity, které návrh zesložiťují a snižují jeho robustnost
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
56 z 124
3.4.3 Příklad ►
Každá třída v příkladu definuje data i chování ● Zde např. objekt ví, zda má smysl k danému datu něco odhadovat
class RevenueRecognition... private Money amount; private MfDate date; public RevenueRecognition(Money amount, MfDate date) { this.amount = amount; this.date = date; } public Money getAmount() { return amount; } boolean isRecognizableBy(MfDate asOf) { return asOf.after(date) || asOf.equals(date); } Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
57 z 124
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
58 z 124
►
Výpočet odhadu výnosu zapojí třídy Contract i RevenueRecognition class Contract... private List revenueRecognitions = new ArrayList(); public Money recognizedRevenue(MfDate asOf) { Money result = Money.dollars(0); Iterator it = revenueRecognitions.iterator(); while (it.hasNext()) { RevenueRecognition r = (RevenueRecognition) it.next(); if (r.isRecognizableBy(asOf)) result = result.add(r.getAmount()); } return result; }
►
V doménovém modelu bývá pro cizího obtížné odhalit, jak jednotlivé třídy spolupracují na řešení (byť malých) úloh ● Musíte vědět, který objekt za co zodpovídá
a proč se jej na to jiní potřebují zeptat ● Koncentrace zodpovědnosti eliminuje duplicity v kódu Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
59 z 124
3.4.4 Příklad: Přidáme strategii ►
Každý produkt je spojen s nějakou strategií odhadu, která určuje, jaký algoritmus pro odhad použít (viz zadání) ● Každá strategie je definována vlastní třídou
class Contract... private Product product; private Money revenue; private MfDate whenSigned; private Long id; public Contract(Product product, Money revenue, MfDate whenSigned) { this.product = product; this.revenue = revenue; this.whenSigned = whenSigned; }
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
60 z 124
class Product... private String name; private RecognitionStrategy recognitionStrategy; public Product(String name, RecognitionStrategy recognitionStrategy) { this.name = name; this.recognitionStrategy = recognitionStrategy; } public static Product newWordProcessor(String name) { return new Product(name, new CompleteRecognitionStrategy()); } public static Product newSpreadsheet(String name) { return new Product(name, new ThreeWayRecognitionStrategy(60, 90)); } public static Product newDatabase(String name) { return new Product(name, new ThreeWayRecognitionStrategy(30, 60)); } Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
61 z 124
class RecognitionStrategy... abstract void calculateRevenueRecognitions(Contract contract); class CompleteRecognitionStrategy... void calculateRevenueRecognitions(Contract contract) { contract.addRevenueRecognition( new RevenueRecognition(contract.getRevenue(), contract.getWhenSigned())); }
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
62 z 124
class ThreeWayRecognitionStrategy... private int firstRecognitionOffset; private int secondRecognitionOffset; public ThreeWayRecognitionStrategy(int firstRecognitionOffset, int secondRecognitionOffset) { this.firstRecognitionOffset = firstRecognitionOffset; this.secondRecognitionOffset = secondRecognitionOffset; } void calculateRevenueRecognitions(Contract contract) { Money[] allocation = contract.getRevenue().allocate(3); contract.addRevenueRecognition( new RevenueRecognition(allocation[0], contract.getWhenSigned())); contract.addRevenueRecognition( new RevenueRecognition (allocation[1], contract.getWhenSigned().addDays(firstRecognitionOffset))); contract.addRevenueRecognition( new RevenueRecognition(allocation[2], contract.getWhenSigned().addDays(secondRecognitionOffset))); } Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
63 z 124
►
Sekvenční diagram ukazuje, jak si jednotlivé třídy předávají informace a jak spolupracují
►
Vzor Strategie poskytuje dobře definovaný bod pro úpravy v případě přidání další strategie
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
64 z 124
3.4.5 Porovnání obou přístupů k řešení Transakční skript vykoná vše sám; požádá datovou bránu o potřebná data, která zpracuje a předá prezentační vrstvě výsledek
V doménovém modelu může mít každý produkt jiný algoritmus pro odhad příjmu, takže objekt Contract požádá objekt Produkt, aby mu to spočítal podle svých nastavení
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
65 z 124
3.4.6 Výhody a nevýhody doménového modelu ►
Výhody ● Jakmile si na něj zvyknete, otevírá se před vámi řada cest,
jak se vypořádat s rostoucí složitostí přehledným způsobem ● Pokud by v předchozím příkladu přibývaly algoritmy výpočtu zisku, mohli bychom vytvářet další objekty typu RecognitionStrategy ● Jakmile se naučíte přemýšlet objektově, začnete tomuto způsobu dávat přednost i v jednoduchých příkladech ►
Nevýhody ● Hlavní problém je, že není hned při vstupu požadavku jasné,
co bude kdo dělat a řešení je rozpuštěné po jednotlivých objektech modelu ● Nováčkům chvíli trvá (často několik měsíců), než si na něj zvyknou ● Složité modely vedou ke složité datové vrstvě; čím komplexnější je model, tím složitější bývá mapování na databázi ● Datová vrstva bývá drahá; jsou to ale fixní náklady, které se časem zaplatí
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
66 z 124
3.5 Tabulkový modul (Table Module) ►
Jedna instance zpracovává logiku pro všechny řádky v tabulce, případně v pohledu
►
Tabulkový modul vypadá na první pohled jako Doménový model; hlavní rozdíl je v tom, že se pro každý výskyt nějaké entity nepoužívá nový objekt, ale vše obhospodařuje jeden objekt ● V našem příkladu by nebyla každá smlouva samostatnou instancí
třídy Contract, ale jedna instance by zastupovala všechny smlouvy
►
Jinými slovy: Tabulkový modul definuje pro každou tabulku v databázi jednu třídu, jejíž instance je jedináček a má na starosti všechny operace týkající se dané tabulky ● Neidentifikuje proto záznam jako instanci (oproti doménovému modelu),
ale jako položku v tabulce – nejčastěji primárním klíčem
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
67 z 124
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
68 z 124
►
Sekvenční diagramy předchozích vzorů
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
69 z 124
3.5.1 Princip ►
Vzor Tabulkový modul je kompromisem mezi vzory Transakční skript a Doménový model
►
Organizuje aplikační logiku kolem tabulek (a ne kolem jednotlivých procedur, jako transakční skript) takže je strukturovanější než transakční skript a snáze se v něm odhalují duplikace
►
„Neznásilňuje“ tolik databázi jako Doménový model, jehož největším problémem je právě to, že se moc „nekamarádí“ s relačními databázemi, které jsou stále s odstupem nejpoužívanější a v jejichž paradigmatu řada vývojářů myslí
►
Na druhou stranu tato organizace svazuje, takže v něm není možno jednoduše použít řadu objektových technik, které využíváme při práci podle vzoru Doménový model ● Dědičnost a polymorfizmus ● Různé návrhové vzory (např. Strategie)
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
70 z 124
►
Jeho koncepce je pro starší vývojáře, kterým se ještě objektové paradigma nedostalo pod kůži, výrazně přijatelnější a pochopitelnější
►
Těsná vazba tabulkového modulu na strukturu databáze, umožňuje využít nejrůznější nástroje orientované na zpracování výsledků SQL dotazů
►
Tabulkový modul pracuje nad tabulkově orientovanými datovými strukturami, které jsou většinou získány jako výsledek SQL dotazu a jsou uchovávány jako sady záznamů (Record Set) ● Poskytuje explicitní rozhraní (tj. sadu metod) pracující přímo s daty ● Sdružením chování s tabulkou umožňuje vhodně zapouzdřit
data spolu s operacemi nad těmito daty
►
Často je k vykonání úkolu potřeba několik tabulkových modulů, které mnohdy pracují nad společnou sadou záznamů
►
Většinou je modul napojen na tabulku v databázi, ale může být napojen i na výsledek dotazu
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
71 z 124
3.5.2 Realizace ►
Tabulkový modul může být instance třídy, ale může jej reprezentovat i třída sama
►
Výhoda použití instance ● Lze ji inicializovat prostřednictvím existující sady dat (record set),
která je např. výsledkem dotazu ● Lze pro ně aplikovat dědičnost, takže můžeme prostým překrytím metod vytvářet specializované moduly odvozené od modulů existujících ►
Tabulkové moduly mohou obsahovat dotazy jako tovární metody
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
72 z 124
►
Alternativou pro dotazy může být tabulková datová brána (table data gateway) ● Při jejím použití se však musí do návrhu přidat
další třída a začlenit její logika ● Na druhou stranu pak můžeme jednoduše přepínat mezi zdroji dat ►
Při použití tabulkové datové brány: 1. Tabulková datová brána vytvoří sadu záznamů (record set) 2. Vytvoří se tabulkový modul s touto sadou jako parametrem 3. Je-li potřeba spolupráce více tabulkových modulů, vytvoří se všechny nad stejnou sadou záznamů 4. Tabulkový modul provede se sadou záznamů požadované akce a předá modifikovanou sadu k zobrazení a editaci 5. Po editaci tabulkový modul výsledná data zvaliduje 6. Validovaná data se uloží do databáze
►
Výhodou tohoto přístupu je, že modul je možno testovat s daty uloženými v paměti, tj. bez přístupu do databáze
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
73 z 124
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
74 z 124
3.5.3 Příklad ►
Příklad řeší situaci, kdy se různě vyhodnocují možné výnosy pro textové procesory, tabulkové kalkulátory a databáze
►
Budeme používat následující datový model:
►
Pro každou tabulku bude definován jeden modul
►
Protože v .NET budou všechny moduly sdílet třídu operující nad sadou záznamů, použijeme architektonický vzor Společný rodiče vrstvy (Layer Supertype)
►
Zdrojový kód bude v C#
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
75 z 124
class TableModule... protected DataTable table; protected TableModule(DataSet ds, String tableName) { table = ds.Tables[tableName]; } ►
Dceřiný konstruktor předá rodiči název tabulky class Contract... public Contract (DataSet ds) : base (ds, "Contracts") {}
►
To umožní vytvořit tabulkový modul pouhým předáním sady záznamů (v .NET se jmenuje dataset) konstruktoru contract = new Contract(dataset);
To umožní oddělit kód vytvářející sadu záznamů od tabulkového modulu, který s ní pracuje Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
76 z 124
►
Pro vyhledání záznamu na základě klíče můžeme v C# využít indexer class Contract... public DataRow this [long key] { get { String filter = String.Format("ID = {0}", key); return table.Select(filter)[0]; } } private Decimal[] allocate(Decimal amount, int by) { Decimal lowResult = amount / by; lowResult = Decimal.Round(lowResult,2); Decimal highResult = lowResult + 0.01m; Decimal[] results = new Decimal[by]; int remainder = (int) amount % by; for (int i = 0; i < remainder; i++) results[i] = highResult; for (int i = remainder; i < by; i++) results[i] = lowResult; return results; }
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
77 z 124
public void CalculateRecognitions (long contractID) { DataRow contractRow = this[contractID]; Decimal amount = (Decimal)contractRow["amount"]; RevenueRecognition rr = new RevenueRecognition (table.DataSet); Product prod = new Product(table.DataSet); long prodID = GetProductId(contractID); if (prod.GetProductType(prodID) == ProductType.WP) { rr.Insert(contractID, amount, (DateTime) GetWhenSigned(contractID)); } else if (prod.GetProductType(prodID) == ProductType.SS) { Decimal[] allocation = allocate(amount,3); rr.Insert(contractID, allocation[0], (DateTime) GetWhenSigned(contractID)); rr.Insert(contractID, allocation[1], (DateTime) GetWhenSigned(contractID).AddDays(60)); rr.Insert(contractID, allocation[2], (DateTime) GetWhenSigned(contractID).AddDays(90)); } Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
78 z 124
else if (prod.GetProductType(prodID) == ProductType.DB) { Decimal[] allocation = allocate(amount,3); rr.Insert(contractID, allocation[0], (DateTime) GetWhenSigned(contractID)); rr.Insert(contractID, allocation[1], (DateTime) GetWhenSigned(contractID).AddDays(30)); rr.Insert(contractID, allocation[2], (DateTime) GetWhenSigned(contractID).AddDays(60)); } else { throw new Exception("invalid product id"); } }
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
79 z 124
3.6 Výběr mezi vzory ►
Vhodnost toho kterého vzoru záleží na složitostí aplikační logiky
►
Doménový model ● Pro velmi jednoduchou logiku nebývá Doménový model atraktivní,
protože cena přípravy modelu a komplexnost zdroje dat se s nejvyšší pravděpodobností nevyplatí ● Jakmile však složitost řešeného problému roste, nemá tento vzor mezi ostatními konkurenci ● Týmy, které s ním umějí pracovat, mají ale tak malé počáteční fixní náklady, že použití vzoru začne být zajímavé i pro malé projekty; čím lepší tým, tím více inklinuje k tomuto modelu ►
Tabulkový modul ● Vhodnost použití záleží na podpoře v použitém vývojovém prostředí ● Např. v .NET nemá vůbec smysl uvažovat o transakčním skriptu ● Neposkytuje-li vývojové prostředí podporu práci se sadami dat,
nemá smysl si komplikovat život tabulkovým modulem
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
80 z 124
►
Rozhodnutí o použití některého architektonického vzoru nemusí být definitivní a je možné jej v budoucnu změnit; musíme ale být připraveni na to, že to něco stojí
►
Přecházení od transakčního skriptu k doménovému modelu je tak obtížné, že je výhodnější začít znovu od začátku
►
Přechod od doménového modelu k transakčnímu skriptu má smysl pouze tehdy, dokážete-li levně zjednodušit datovou vrstvu
►
Použití uvedených vzorů se vzájemně nevylučuje; často se používá pro část logiky transakční skript a pro zbytek tabulkový modul
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
81 z 124
3.6.1 Ještě jednou grafické srovnání
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
82 z 124
3.7 Servisní vrstva ►
Doménová vrstva bývá často rozdělena do dvou vrstev ● Horní vrstvu tvoří servisní vrstva ● Spodní vrstvu tvoří doménový
model nebo tabulkový modul
►
Servisní vrstva představuje hranici aplikace definující služby, které může aplikace poskytovat, a koordinující odpovědi aplikace na každý požadavek
►
Vzhledem k možnosti definovat přehledné API bývá do servisní vrstvy umisťována správa bezpečnosti a někdy i transakcí
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
83 z 124
►
Toto uspořádání poskytuje jednoduchý model definující v servisní vrstvě všechny veřejné metody spolu s popisem jejich transakčních a bezpečnostních charakteristik ● Specifikace těchto charakteristik je možno zadat
v konfiguračních souborech, ● prostřednictvím anotací přímo v programu ●
►
Při použití servisní vrstvy je klíčovým rozhodnutím specifikace toho, co vše bude mít servisní vrstva na starosti ● V minimalistickém případě slouží pouze jako fasáda
takže všechny klíčové činnosti se dějí pod ní a ona pouze deleguje dále obdržené požadavky ● Druhý extrém má většinu logiky umístěnou v transakčních skriptech uvnitř servisní vrstvy ● Podpůrný doménový objekt je pak velice jednoduchý ● Jedná-li se o doménový model, bude přímo namapován na databázi a může použít jednoduchou datovou vrstvu typu Aktivní záznam (Active Record) ● Mezi těmito krajnostmi je styl ovladač – entita (entity-controller style) označovaný také jako ovladač případů užití (use-case controller) Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
84 z 124
3.7.1 Doporučení: Martin Fowler: ►
Rozhodnete-li se pro doménový model, svěřte mu všechny klíčové činnosti
►
Servisní vrstva by měla být co nejtenčí a úkoly by jí měly být svěřovány až v případě potřeby
►
Někteří začínají bez ní a zavedou ji až ve chvíli, kdy o to „aplikace požádá“
Randy Staffor ►
§
Preferuje silnější servisní vrstvu, která maximálně odstíní aplikační logiku od tříd, které s aplikací spolupracují
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
85 z 124
4. Obecné vzory Obsah 4.1 Oddělené rozhraní (Separated Interface) 4.1.1 Způsob implementace 4.1.2 Použití 4.2 Zásuvný modul (Plugin) 4.2.1 Způsob implementace 4.2.2 Příklad: Generátor ID 4.3 Brána (Gateway) 4.3.1 Motivace 4.3.2 Způsob realizace
4.3.3 Srovnání se vzory z GoF 4.3.4 Příklad: Vlastní systém zpráv 4.4 Testovací záslepka (Service Stub) 4.4.1 Způsob realizace 4.4.2 Příklad: Test brány 4.5 Mapovač (Mapper) 4.5.1 Princip funkce 4.5.2 Použití 4.6 Přepravka (Data Transfer Object)
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
86 z 124
4.1 Oddělené rozhraní (Separated Interface) ►
Definuje rozhraní v jiném balíčku než je jeho implementace
►
Použijeme jej např. tehdy, potřebuje-li třída volat třídu z vyšší vrstvy
►
Klient potřebující oslovit objekt s tímto rozhraním se nestará o to, kdo přesně jej implementuje
►
Rozhraní nemusíme vždy definovat jako interface, někdy je vhodnější abstraktní třída
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
87 z 124
4.1.1 Způsob implementace ►
Jsou-li všichni klienti ve stejném balíčku, lze interface umístit k nim, jsou-li v různých balíčcích, definujeme separátní balíček pro interface
►
Klienti vyhlašují, že budou spolupracovat s každým, kdo bude implementovat daný interface
►
Je třeba vybrat, kdo bude zodpovědný za definici interfejsu ● Definují-li jej vývojáři klientské části,
mohou do něj vhodně zahrnout své požadavky a potřeby ● Definují-li jej vývojáři implementační části, mohou do něj vhodně zahrnout, co vše mohou klientům nabídnout ►
Problematickým se může ukázat vytvoření instance ● Buď musím znát implementující třídu, abych zavolal konstruktor ● Nebo vytvořím tovární třídu, ale tu opět musím znát ● Jednou z možností je využít plugin (viz dále) s tovární třídou
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
88 z 124
4.1.2 Použití ►
Použijeme jej v okamžiku, kdy budeme chtít odstranit závislost mezi dvěma částmi systému
Příklady: ►
Připravíte ve frameworku kód, který potřebuje volat metody do něj zasazené aplikace
►
Kód v jedné vrstvě potřebuje volat kód v jiné vrstvě
►
Kód vyvinutý jednou skupinou potřebuje komunikovat s kódem vyvinutým jinou skupinou
►
Oddělený interface je vhodným řešením při definici brány
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
89 z 124
4.2 Zásuvný modul (Plugin) ►
Nepropojí třídy při překladu, ale až při konfiguraci
►
Občas není při návrhu kódu jasné, s jakými externími moduly bude kód spolupracovat; jediné co známe je jeho oddělené rozhraní ● Často potřebujeme testovat s jinými (testovacími) moduly,
než se kterými pak bude aplikace provozována ● V řadě případů se za života aplikace některé moduly změní, a bylo by vhodné se změně přizpůsobit, aniž bychom museli aplikaci upravovat a znovu překládat a instalovat ● Definujeme-li standardní tovární metodu se sérií rozhodování, stává se kód pří více různých variantách nepřehledný a obtížně spravovatelný Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
90 z 124
4.2.1 Způsob implementace ►
Aplikace musí mít dostupné oddělené rozhraní, v němž je definováno požadované chování daného modulu
►
Paralelně definujeme tovární třídu těchto modulů (plugin factory), která si v definovaném externím zdroji zjistí aktuální nastavení, a podle něj se pak napojí na příslušné třídy
►
Jako onen externí zdroj se často používá textový soubor, v němž je uvedeno, která třída bude implementovat které rozhraní
►
Mechanizmus funguje nejlépe v jazycích podporujících reflexi pak lze příslušnou instanci vytvořit bez spolupráce s překladačem ● Soubor pak musí definovat mapování názvů rozhraní
na názvy tříd, které je implementují ● Tovární třída je pak nezávislá na případných změnách konfigurace včetně možnosti přidání zcela nových implementačních tříd ►
Jazyky nepodporující reflexi to řeší podmíněnými příkazy, nicméně všechny použité třídy musí být známy při překladu
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
91 z 124
4.2.2 Příklad: Generátor ID
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
92 z 124
►
Definujeme oddělené rozhraní specifikující požadovanou funkčnost interface IdGenerator... public Long nextId();
►
Testovací generátor bude vracet posloupnost čísel class Counter implements IdGenerator... private long count = 0; public synchronized Long nextId() { return new Long(count++); }
►
Generátor komunikující s databází bude vracet další ID z definované posloupnosti v definovaném zdroji class OracleIdGenerator implements IdGenerator... public OracleIdGenerator() { this.sequence = Environment.getProperty("id.sequence"); this.datasource = Environment.getProperty("id.source"); }
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
93 z 124
►
Tovární třída zjistí při zavádění programu aktuální nastavení a podle toho připraví příslušné implementující třídy class PluginFactory... private static Properties props = new Properties(); static { try { props.load(PluginFactory.class. getResourceAsStream("/plugins.properties")); } catch (Exception ex) { throw new ExceptionInInitializerError(ex); } }
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
94 z 124
►
Tovární metoda pak už jenom poskytne instanci známého zdroje public static Object getPlugin(Class iface) { String implName = props.getProperty(iface.getName()); if (implName == null) { throw new RuntimeException("\nImplementation not specified for " + iface.getName() + " in PluginFactory propeties."); } try { return Class.forName(implName).newInstance(); } catch (Exception ex) { throw new RuntimeException( "\nFactory unable to construct instance of " + iface.getName()); } }
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
95 z 124
►
Pokud bychom v rozhraní IdGenerator definovali navíc atribut: IdGenerator INSTANCE = (IdGenerator) PluginFactory.getPlugin(IdGenerator.class);
mohli bychom pak definovat: class Customer extends DomainObject... private Customer(String name, Long id) { super(id); this.name = name; } public Customer create(String name) { Long newObjId = IdGenerator.INSTANCE.nextId(); Customer obj = new Customer(name, newObjId); obj.markNew(); return obj; } Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
96 z 124
4.3 Brána (Gateway) ►
Objekt, který zapouzdří přístup k externímu systému či zdrojům Propojený obrázek nelze zobrazit. Příslušný soubor by l prav děpodobně přesunut, přejmenov án nebo odstraněn. Ov ěřte, zda propojení odk azuje na správ ný soubor a umístění.
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
97 z 124
4.3.1 Motivace ►
Program musí často spolupracovat s nejrůznějšími externími součástmi, z nichž mnohé ani nejsou objektové ● Relační databáze, XML soubory, …
►
Tyto zdroje mají své API, ale to je většinou poplatné charakteru daného zdroje, a nevychází vstříc naší aplikaci; to komplikuje ● spolupráci s těmito zdroji, protože se musíme učit nestandardní API ● zabezpečení přenosu informací mezi těmito zdroji ● budou úpravy kódu, protože se opět musíme přizpůsobovat
►
Řešením je aplikace návrhového vzoru Adaptér: vytvoření zástupného objektu – brány, který zapouzdří komunikaci s daným externím subjektem a nabídne zbytku systému API odpovídající standardním zvyklostem
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
98 z 124
4.3.2 Způsob realizace ►
V praxi patří tento vzor k nejjednodušším; je vlastně sloučením vzorů Fasáda a Adaptér z GoF
►
Zapouzdřující API (a s ním i příslušnou třídu) je vhodné definovat co nejjednodušší – mělo by pouze zprostředkovat komunikaci s příslušným objektem a nemělo by nabízet implementaci žádné nadstavbové logiky
►
V řadě případů je vhodné rozdělit činnost brány mezi více objektů – např. mezi „front end“ a „back end“ ● „Back end“ působí pouze jako zprostředkovatel komunikace
a nijak se nesnaží upravovat API externího objektu ● „Front end“ převádí toto těžkopádné API do přijatelnější podoby ● Uplatní se zejména v případech složitější adaptace
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
99 z 124
4.3.3 Srovnání se vzory z GoF ►
Fasáda ● Fasádu vytváří autor složitého systému, aby zjednodušil složité API,
kdežto bránu vytváří klient pro své potřeby ● Fasáda často nabízí alternativní, zjednodušené API, kdežto brána je často zcela zabalí a nahradí vlastním ►
Adaptér ● Adaptér většinou převádí jedno rozhraní na jiné, kdežto objekt zabalený
bránou často nemá žádné v dané aplikaci použitelné rozhraní ● Někdy můžeme použít adaptér k mapování funkcí a pak se stává součástí brány ►
Prostředník (Mediator) ● Odděluje objekty, aby spolu mohly komunikovat, aniž by o sobě věděly,
kdežto u brány ostatní oslovuji zabalený objekt, který jediný o ostatních neví
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
100 z 124
4.3.4 Příklad: Vlastní systém zpráv ►
Systémy zabezpečující rozesílání zpráv publikují metody typu: int send(String messageType, Object[] args);
které mohou zkontrolovat typ zprávy a jeho parametry až za běhu ►
Abychom mohli kontrolovat správné volání metody při překladu, bylo by vhodnější, kdyby měla signaturu např. ve tvaru: public void sendConfirmation(String orderID, int amount, String symbol);
Pak bychom mohli v době překladu kontrolovat počet parametrů i jejich typy
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
101 z 124
►
Naše doménové objekty by pak mohly posílat zprávy následovně: class Order... public void confirm() { if (isValid()) { Environment.getMessageGateway(). sendConfirmation(id, amount, symbol); } }
►
Zůstává problém s oznamováním chybových stavů, které programy v C standardně posílají v návratové hodnotě; mohli bychom ale definovat: public static final int NULL_PARAMETER = ‐1; public static final int UNKNOWN_MESSAGE_TYPE = ‐2; public static final int SUCCESS = 0;
a definovat metody brány
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
102 z 124
class MessageGateway... protected static final String CONFIRM = "CNFRM"; private MessageSender sender; public void sendConfirmation(String orderID, int amount, String symbol) { Object[] args = new Object[]{orderID, new Integer(amount), symbol}; send(CONFIRM, args); } private void send(String msg, Object[] args) { int returnCode = doSend(msg, args); if (returnCode == MessageSender.NULL_PARAMETER) throw new NullPointerException( "\nNull Parameter bassed for msg type: " + msg); if (returnCode != MessageSender.SUCCESS) throw new IllegalStateException( "\nUnexpected error from messaging system #:" + returnCode); } protected int doSend(String msg, Object[] args) { Assert.notNull(sender); return sender.send(msg, args); } Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
103 z 124
4.4 Testovací záslepka (Service Stub) ►
Odstraňuje závislost na problematických službách při testování
►
Testovací záslepka bývá občas označována jako Mock Object
►
Chování externích služeb během testování je občas nepředvídatelné, jindy jsou v době testování nepoužitelné či dokonce nehotové
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
104 z 124
4.4.1 Způsob realizace ►
Prvním krokem by mělo být vytvoření Brány, která by měla být definována jako Oddělené rozhraní ● Jedna jeho implementace bude řešit testovací modul ● Druhá implementace bude řešit produkční prostředí
►
Příslušná implementace brány bude zavedena jako Plugin
►
Základ úspěchu je řešit vše maximálně jednoduše
►
Nahrazením takové služby lokálním objektem se navíc může vše výrazně zrychlit
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
105 z 124
4.4.2 Příklad: Test brány ►
Budeme testovat systém zpráv z minulé kapitoly
►
Na konci předvedeného kódu byla metoda doSend(String,Object[]), která sloužila právě k tomu, abychom mohli definovat alternativní verzi pro testování
►
Připomenu: protected int doSend(String msg, Object[] args) { Assert.notNull(sender); return sender.send(msg, args); }
►
Třída záslepky je v následující ukázce definována jako potomek třídy z produkční verze, který překrývá zděděnou metodu doSend(String,Object[]), abychom se při testování obešly bez systému posílání zpráv
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
106 z 124
class MessageGatewayStub... protected int doSend(String messageType, Object[] args) { int returnCode = isMessageValid(messageType, args); if (returnCode == MessageSender.SUCCESS) { messagesSent++; } return returnCode; } private int isMessageValid(String messageType, Object[] args) { if (shouldFailAllMessages) return ‐999; if (!legalMessageTypes().contains(messageType)) return MessageSender.UNKNOWN_MESSAGE_TYPE; for (int i = 0; i < args.length; i++) { Object arg = args[i]; if (arg == null) { return MessageSender.NULL_PARAMETER; } } return MessageSender.SUCCESS; } Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
107 z 124
public static List legalMessageTypes() { List result = new ArrayList(); result.add(CONFIRM); return result; } private boolean shouldFailAllMessages = false; public void failAllMessages() { shouldFailAllMessages = true; } public int getNumberOfMessagesSent() { return messagesSent; } ►
Poslední metoda pomáhá otestovat, že brána pracuje korektně s takovýmito testy – viz následující stránka
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
108 z 124
class GatewayTester... public void testSendNullArg() { try { gate().sendConfirmation(null, 5, "US"); fail("Didn't detect null argument"); } catch (NullPointerException expected) { } assertEquals(0, gate().getNumberOfMessagesSent()); } private MessageGatewayStub gate() { return (MessageGatewayStub) Environment.getMessageGateway(); } @Before protected void setUp() throws Exception { Environment.testInit(); } Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
109 z 124
4.5 Mapovač (Mapper) ►
Objekt, který nastavuje komunikaci mezi dvěma nezávislými objekty
►
Potřebujete-li zprostředkovat komunikaci mezi systémy, u nichž nechcete, aby o sobě věděly (aby byly na sobě závislé)
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
110 z 124
4.5.1 Princip funkce ►
Mapovač slouží jako izolační vrstva mezi dvěma subsystémy, aniž by o něm tyto subsystémy věděly
►
Nejsložitější je u něj vyřešení otázky, jak jej zavolat, protože nemůže být volán přímo ani jedním ze subsystémů, jejichž komunikaci zprostředkovává ● Někdy se jako prostředník používá třetí subsystém ● Někdy se používá to,
že mapovač se přihlásí jako posluchač některého ze systémů, aby od něj získával informace předávané druhému
►
Je podobný vzoru Prostředník (Mediator), ale na rozdíl od prostředníka o mapovači komunikující objekty nevědí
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
111 z 124
4.5.2 Použití ►
Při oddělování částí systému se architekt většinou vybírá mezi vzory Brána (Gateway) a Mapovač (Mapper)
►
Brána je jednodušší jak v návrhu, tak při použití, a proto je také mnohem více používaná
►
Po mapovači sáhneme, když chceme mít oba systémy nezávislé, tj. aby ani jeden nezávisel na tom druhém
►
Podnikové aplikace jej proto většinou používají při komunikaci s databází jako vzor Datový mapovač (Data Mapper)
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
112 z 124
4.6 Přepravka (Data Transfer Object) ►
§
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
113 z 124
5. Vzory pro zdroje dat Obsah 5.1 Sada záznamů (Record Set) 5.1.1 Princip práce 5.1.2 Explicitní rozhraní 5.2 Tabulková datová brána (Table Data Gateway)
5.3 Řádková datová brána (Row Data Gateway) 5.4 Aktivní záznam (Active Record) 5.5 Identifikační pole (Identity field) 5.6 Datový mapovač (Data Mapper)
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
114 z 124
5.1 Sada záznamů (Record Set) ►
Reprezentace dat z databáze v paměti ● Reaguje na rozšířenost tabulkových (= relačních) databází;
pracujete-li prostřednictvím nástroje přímo s daty v databázi, nemáte kam vložit validaci a aplikační logiku (GUI ani vložené procedury nejsou tím správným místem) ● Poskytuje strukturu vypadající stejně jako výsledek SQL dotazu, ale přitom s ním může být zacházeno jako s řádnou součástí programu
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
115 z 124
5.1.1 Princip práce ►
Kód vytvářející objekty schopné vystupovat jako sady záznamů vytvářejí autoři obsluhovaných databází
►
Základní vlastností této sady je, že vypadá naprosto stejně jako výsledek dotazu ● Jejím prostřednictvím tak můžeme s daty jednoduše manipulovat ● V případě potřeby není velký problém definovat vlastní,
protože její chování je dostatečně průzračné – můžeme ji definovat např. jako seznam map
►
Výhodou je, že sada nemusí být průběžně napojena na databázi, a můžeme ji proto poslat jako objekt k dalšímu zpracování
►
Problémy s ukládáním změn zpět do databáze mohou být řešeny tak, že definujeme sadu záznamů jako formu Pracovní jednotky (Unit of Work) – viz dále
►
Zdroj data používá většinou optimistické zamykání
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
116 z 124
5.1.2 Explicitní rozhraní ►
Většina sad používá implicitní rozhraní ● Při získávání dat aktivujete generickou metodu,
jejíž parametr indikuje pole, jehož obsah chcete získat – např. aReservation["passenger"]
►
Explicitní rozhraní vyžaduje definici třídy daného typu a odpovídajících metod aReservation.passenger
►
Implicitní verze je zdánlivě flexibilnější, ale na druhou stranu neposkytuje on-line nápovědu ani kontrolu správnosti v době překladu ● Potřebuji-li např. zjistit, kdo má zamluvenou rezervaci,
musím někde zjistit, že se doptám řetězcem „passenger“
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
117 z 124
►
Problém se zhoršuje u staticky typovaných jazyků, v nichž bych příjmení rezervanta získával např. příkazem ((Person)aReservation["passenger"]).lastName
v jehož průběhu ztratí překladač veškeré typové informace ►
Při explicitním rozhraní bych použil příkaz: aReservation.passenger.lastName
►
Naštěstí sady záznamů poskytované databázemi poskytují vlastnosti, jejichž prostřednictvím se můžeme doptat na správný název i typ jednotlivých položek
►
Obecně je třeba přiznat, že implicitní rozhraní je používáno častěji
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
118 z 124
5.2 Tabulková datová brána (Table Data Gateway) ►
Objekt sloužící jako brána do databázové tabulky; jedna instance brány má na starosti všechny záznamy
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
119 z 124
5.3 Řádková datová brána (Row Data Gateway) ►
Objekt sloužící jako brána k záznamu v tabulce; pro každý záznam existuje jeho vlastní instance
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
120 z 124
5.4 Aktivní záznam (Active Record) ►
Objekt, jenž zabalí záznam v tabulce či pohledu, zapouzdří přístup k databázi a přidá k zabaleným datům aplikační (doménovou) logiku
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
121 z 124
5.5 Identifikační pole (Identity field) ►
Uloží databázové identifikační pole do objektu, aby se dala snadno spravovat shoda mezi záznamem v paměti a databázi
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
122 z 124
5.6 Datový mapovač (Data Mapper) ►
§
Vrstva mapovačů přenášejících data mezi objekty a databází, přičemž udržuje oba partnery vzájemně nezávislé
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
123 z 124
6. KONEC
§
Děkuji za pozornost
Copyright © Rudolf Pecinovský, Soubor: Architektonické vzory.doc, verze 1.00.2413, uloženo po 26.11.2012 – 13:37
124 z 124