úterý 26. dubna 2011

Vícevrstvé architektury aplikací

Vrstvy ve světě programových systémů

Vrstvení (layering) je jednou ze základních technik, používanou návrháři systému, pro rozložení složitosti komplexních programových systémů. Je pravděpodobné, že pokud čtete tento příspěvek, používáte jeden z několika webových prohlížečů. Prohlížeč je pro Vás jednou z aplikací, která řeší požadovanou funkcionalitu. Pokud se však pokusíte dekomponovat jednotlivé využívané softwarové vrstvy, začnete se zanořovat takto (zjednodušeně):

  • aplikační vrstva (webový prohlížeč Chrome, Firefox, IE, ...)
  • vrstva operačního systému (Windows, Linux, Mac OS, Android, iOS, ...)
  • vrstva ovladačů zařízení (ovladač klávesnice, grafické karty, zvukové karty, ...)
  • vrstva instrukcí CPU, GPU, ... (instrukce na nejnižší hardwarové úrovni)

Webový prohlížeč načítá webový obsah z webových serverů. Využívá k tomu TCP/IP komunikační protokol, který má ve zjednodušené definici tyto čtyři vrstvy:

  • aplikační vrstva (programy nebo služby, které využívají přenos dat po síti ke konkrétním službám pro uživatele - FTP, HTTP, DNS, ...)
  • transportní vrstva (poskytuje transportní služby a je implementována v koncových zařízeních (počítačích))
  • síťová vrstva (zajišťuje síťovou adresaci, směrování a předávání datagramů, je implementována ve všech prvcích sítě - směrovače i počítače)
  • vrstva síťového rozhraní (umožňuje přístup k fyzickému přenosovému médiu, implementace je závislá na typu sítě)

Vrstvy lze uspořádat podle úrovně, na které se vyskytují. V našich příkladech je nejvyšší vrstvou aplikační vrstva (webový prohlížeč) a nejnižší vrstva instrukcí CPU. Toto uspořádání je důležité z pohledu závislostí jednotlivých vrstev. Vždy platí pravidlo, že vyšší vrstva využívá služeb pouze nižších vrstev a obvykle pouze o jednu úroveň nižší. Nižší vrstva tedy nic neví o vyšších vrstvách, pouze nabízí rozhraní, které je známé vyšší vrstvě.

Výhody a nevýhody použití vrstev

Rozdělení systémů do vrstev má několik významných výhod:

  • Zjednodušení složitých systémů. Stačí pochopit rozhraní a pravidla použití vrstvy, kterou chcete využívat a dokážete využít celý (jinak složitý) podsystém. Např. pokud se naučíte pracovat s FTP službou, začnete využívat celý TCP/IP komunikační protokol. Pokud byste se chtěli naučit celý TCP/IP protokol, dostanete se na úroveň optických vláken a jiných fyzických přenosových médií na nejnižší vrstvě, kterou nikdy přímo využívat nebudete. Takové znalosti jsou samozřejmě vítané, ale ne nezbytně nutné pro práci s FTP.
  • Možnost záměny implementace jedné z vrstev. Například na nejnižší síťové vrstvě TCP/IP protokolu mohou být data přenášena přes technologie PPP, Enhernet, ISDN, DSL nebo další. Každá technologie má svou implementaci a poskytuje služby, které daná vrstva poskytuje. Vyšší vrstvy záměnu implementace nižší vrstvy nepoznají a funkcionalita zůstane zachována.
  • Minimalizace závislostí mezi vrstvami. Souvisí se záměnou implementace vrstvy, viz. předchozí výhoda.
  • Standardizace funkcionality. Příkladem může být náš oblíbený TCP/IP protokol, který je v současnosti standardem pro síťovou komunikaci. Je využíván přes většinu operačních systémů a síťových zařízení. Pro vývojáře programových systémů je přítomnost standardizovaných vrstev dobrá zpráva. Jsou snižovány náklady na vývoj a jsou k dispozici komponenty, které umí standardizované vrstvy využívat.
  • Efektivnější testovatelnost. Pokud složitější funkcionalitu rozdělíte do menších částí (vrstev), můžete efektivněji pokrýt funkcionalitu testy. Můžete také některou z vrstev nahradit falešným mock objektem, tzn. testujete-li například vyšší vrstvu a potřebujete vnutit určité chování nižší vrstvě, tuto nižší vrstvu "namockujete".

Používání vrstev může samozřejmě přinášet i nevýhody:

  • Kaskádové změny. Jedná se o problém typický pro všechny závislé entity. Pokud dojde v nižší vrstvě ke změně, která vynutí změnu veřejného rozhraní této vrstvy, musí se změnit také vrstva vyšší. Konkrétně místo, kde se volá funkcionalita změněné nižší vrstvy. U podnikových aplikací je tato situace typická v případě přidání databázového sloupce. Musí se upravit datová vrstva, doménová logika i přidat zobrazení nového sloupce (viz. Základní aplikační vrstvy podnikových aplikací).
  • Snížení výkonu systému. Přidání každé další vrstvy může způsobit snížení celkového výkonu systému. Každá vrstva má vlastní reprezentaci datových entit. Během předávání datových entit mezi vrstvami musí být tyto entity transformovány. Transformace vyžaduje určitý výpočetní výkon a dojde tedy ke zpomalení. Zpomalení způsobená transformací mohou být kompenzována optimalizací na úrovni jednotlivých vrstev.

Evoluce aplikačních vrstev podnikových systémů

Než se dopracujeme k doporučovanému rozdělení aplikace do logických vrstev, podívejme se krátce na historický proces evoluce podnikových aplikačních systémů. Pro některé z nás to bude ještě stále aktuální téma.

V devadesátých letech byla populární architektura klient-server systémů. Jednalo se o dvouvrstvou architekturu, ve které klient obsahoval kromě uživatelského rozhraní i aplikační logiku a na serveru byla pouze relační databáze. Jako vývojová prostředí pro klientské aplikace se používaly VisualBasic, PowerBuilder a Delphi. V těchto prostředích bylo snadné a rychlé napsat aplikace intenzivně pracující s databázovými daty. Byly k dispozici vizuální uživatelské prvky (např. Datawindows v PowerBuilderu), které obsahovaly přímé propojení na databázi a pomocí SQL manipulovaly s relačními daty.

Pokud aplikace obsahovala pouze zobrazování a jednoduchou aktualizaci dat, bylo řešení přes databázové UI prvky poměrně efektivní. Postupně však docházelo ke změnám požadavků kladeným na tyto aplikace. Začala se více implementovat doménová logika a její pravidla při aktualizaci dat, validační pravidla a výpočty. Bylo běžné, že se kód doménové logiky přidával do UI prvků. Znovupoužitelnost kódu míchající doménovou a prezentační logiku začala být problematická a docházelo k duplikování kódu. Zásahy a změny v kódu se stávaly problematickými.

Jako alternativa k uložení doménové logiky se začaly používat uložené databázové procedury. Možnosti vložených procedur jsou však oproti programovacím jazykům značně omezené. Vývoj, ladění a testování databázových procedur není příliš efektivní. Navíc podnikové aplikace jsou schopny pracovat nad více typy databázových serverů (Oracle, MS SQL Server, Informix). To znamená, že jedna procedura se musí napsat, odladit a udržovat ve více verzích pro cílový databázový systém.

Odpovědí na výše popsané problémy architektury klient-server se stal nástup objektově orientovaného návrhu. Objektový přístup oddělil doménovou logiku od UI do samostatné vrstvy a zadefinoval třívrstvou architekturu (viz. další kapitola).

Martin Fowler označil nástup webových technologií za seizmický šok. Začalo být moderní i účelné distribuovat podnikové aplikace přes webový prohlížeč. V případě klient-server aplikací nebylo snadné jejich část zpřístupnit přes webový prohlížeč (UI a doménová logika byly úzce propojeny). Naproti tomu u vhodně navržených třívrstvých aplikací bylo možné zaměnit prezentační UI vrstvu za prezentační vrstvu tenkého webového klienta. To byl rozhodující argument, který posadil třívrstvou architekturu na koně a ta se následně v efektivitě rychlým tempem vzdalovala pokulhávající dvouvrstvé klient-server architektuře.

Základní aplikační vrstvy podnikových systémů

V předchozím textu jsem uvedl argumenty, proč se na podnikovou aplikaci dívat jako na soubor logických vrstev. Uvedli jsme si také důvody, proč je třívrstvá aplikace považována za efektivní způsob návrhu. Podívejme se nyní podrobněji na jednotlivé vrstvy:

Vrstva Zodpovědnost
Prezentační Zajišťuje služby týkající se uživatelského rozhraní, zobrazování informací, zachycení uživatelských událostí (myš, klávesnice, hlas), zachycení HTTP požadavků, vyvolání přes příkazový řádek, dávkové API.
Doménová Logika, která odpovídá povaze řešené problematiky. Má předobraz v reálném světě.
Datová Komunikace s databází (obecně perzistentní úložiště), systémy pro zasílání zpráv, správa transakcí a další balíčky.

Datová vrstva zajišťuje komunikaci s databází, řídí připojení a odpojení od databázového zdroje, spravuje databázové relace (sessions), zajišťuje podporu transakcí, poskytuje DAO (Data Access Object) objekty realizující CRUD (Create Read Update Delete) funkcionalitu pro jednotlivé třídy objektů. Zajišťuje dále mapování databázových struktur na objekty příslušných tříd. S výhodou lze využít některý z perzistentních frameworků typu NHibernate (v Java světě Hibernate), který řeší záležitosti typu Unit of Work, Identity Map a Lazy Load, které byste jinak museli implementovat sami.

Doménová vrstva nebo také vrstva obchodní logiky (business logic). Tato vrstva obsahuje třídy reprezentující entity řešené domény a třídy zajišťující doménová pravidla. Provádí výpočty a datové manipulace na základě vstupů a uložených dat.

Prezentační vrstva zajišťuje služby týkající se uživatelského rozhraní. Uživatelské rozhraní (UI - User Interface) je závislé na platformě, na které se aplikace provozuje. Může se jednat o WinForm aplikaci, webového klienta, Silverlight, Android klienta, konzolovou aplikaci, dávkové spouštění, apod. Uživatelské rozhraní je tvořeno ovládacími prvky, které nabízejí uživatelskou přívětivost závislou na cílové platformě. Podle požadavků na přívětivost UI je vybrána cílová platforma. Obvykle platí to, že čím bohatší ovládací prvky, tím větší provázanost na konkrétní platformu. Např. webové rozhraní je co do ovládacích prvků "chudobné", ale zobrazitelné na všech platformách, na kterých je k dispozici webový prohlížeč.

Je běžné, že aplikace má jednu datovou vrstvu, jednu doménovou vrstvu a jednu nebo více prezentačních vrstev. Podniková aplikace může nabízet pro uživatele, kteří navádí data a zpracovávají denní agendy, komfortnější rozhraní ve formě uživatelsky bohatého (rich) klienta. Jiná skupina uživatelů může prohlížet výstupy (sestavy) přes webové rozhraní. K dispozici může také být manažerská nádstavba přístupná přes chytré telefony jako Android nebo iPad aplikace. Aplikace může také nabízet programové API, například ve formě webových služeb, pro využití jinými podnikovými systémy.

Třívrstvá architektura je vhodná pro aplikace různých velikostí a složitostí. I u jednoduchých aplikací sestávajících např. z načtení údajů z příkazové řádky, z přepočtu a následného výpisu na obrazovku, je vhodné oddělit logiku odpovídající vrstvám do samostatných metod v rámci jedné třídy. U aplikací složitějších je vhodné oddělení logiky vrstev do samostatných tříd. U komplexnějších aplikací do samostatných balíčků nebo skupin balíčků. Jinak řečeno, žádná aplikace není natolik malá, aby při jejím návrhu nestálo za to uvažovat o rozdělení funkcionality do vrstev.

Třívrstvost je pouze základní logický koncept dnešních architektur. Určitě je možné některou z vrstev rozdělit na více specializovaných vrstev. Můžete také například mezi doménovou vrstvu a prezenční vrstvu vložit vrstvu služeb, která zajistí komunikaci mezi oběma vrstvami a navíc Vám připraví rozhraní pro zpřístupnění Vaší doménové logiky programovým systémům třetích stran.

Fyzické umístění vrstev

Třívrstvá architektura popsaná v předchozí kapitole je logickým pohledem na systém. Neméně důležité rozhodnutí při návrhu systému se týká použití fyzických prvků architektury pro provozování jednotlivých vrstev.

Nejjednodušší formou fyzického modelu je umístění všech vrstev na jeden server a klientský přístup přes webové rozhraní ("tenký" klient). Klient přistupuje k aplikaci přes webové GUI a každá uživatelská interakce může vynutit odeslání požadavku na server, který požadavek zpracuje a zašle odpověď. Tato architekura vyžaduje trvalé připojení k webovému serveru a proto není vhodná pro aplikace, u kterých je požadováno offline použití a následná synchronizace.

Pokud je na klientské straně vyžadováno uživatelsky "bohatší" GUI (rich client), je nutno přesunout minimálně část prezentační vrstvy na klientský počítač. V některých případech je přesunuta i doménová vrstva a také část nebo celá datová vrstva. Takový klient je označován jako "tlustý" nebo "tučný". Distribuce tučného klienta je obvykle problémovější než serverová instalace. Na každé klientské stanici musí být navíc připraveno prostředí vyžadované pro spuštění aplikace - databázový klient, .Net FW, JRE, apod.

Programátorské nešvary při (ne)použití architektury vrstev

  • Umístění funkcionality do nesprávné vrstvy. Častý nešvar, který se obvykle projevuje tak, že do prezenční vrstvy umístíte pravidlo nebo logiku z doménové vrstvy. Např. mějme požadavek, aby ve výpisu přehledu hospodaření za jednotlivé roky byly červeně vyznačeny ty hodnoty položky, které jsou oproti loňsku alespoň o 10% lepší. Chybným postupem je nastavit formátování položky přímo v grafickém ovládacím prvku. Správně je na doménové vrstvě zadefinovat vyhodnocení tohoto pravidla, které zavolá prezenční vrstva a výsledek aplikuje na příslušnou vlastnost (barvu) položky.
  • Nedodržení závislostí pouze z vyšší vrstvy na nižší. Víme, že prezentační vrstva může být závislá na doménové a ta zase pouze na datové. Pokud se stane, že datová vrstva vytvoří závislost na doménové, může nejen dojít k zacyklení závislostí, ale především se stává takovýto systém problematicky použitelným.
  • Prezentační vrstva je závislá na datové. Některé ovládací prvky mohou přímo využívat datový zdroj, který je určen např. SQL příkazem. Taková funkcionalita začne být problematická v situaci, kdy chcete na výběr aplikovat doménovou logiku, např. autorizovat přístup uživatele k datům. Vhodnější je adresovat nějakou třídu v doménové vrstvě, která potřebné omezení aplikuje a vrátí požadovaný výsledek.
  • Nerespektování vrstev. Ať už z neznalosti nebo lenosti se programátoři pravidlem rozdělení do vrstev neřídí vůbec. Systém pak připomíná dort pejska a kočičky, ve kterém je všechno smícháno dohromady. Udržovatelnost systému a navigace v kódu je problematická.

Informační zdroje

  • Martin Fowler: Patterns of Enterprise Application Architecture