Architektura Modelu

V moderních aplikacích na platformě .NET se začínají prosazovat nejrůznější návrhové vzory, což jistě přispívá nejen k lepšímu návrhu aplikací, ale i ke snadnější komunikaci mezi vývojáři. Dosud jsem blogoval především o návrhovém vzoru, který se týkal pouze návrhu Prezentace – Model-View-Controller. Existují ale i jiné prezentační vzory, také Model-View-Presenter a jeho klony se těší velké popularitě (např. v ASP.NET WebForms a WPF/Silverlightu). Tyto návrhové vzory ale neřeší návrh celé aplikace, řeší jen Prezentaci. Zbytek aplikace je nazajímá – ten schovávají za písmenko MModel. Za Model se tedy v tomto případě považuje vše, co není Prezentace (front-end), tedy back-end. A právě návrhu Modelu (back-endu) bych se chtěl v tomto článku (a následujících) věnovat.

Třívrstvá architektura

Po dlouhá léta byla standardem pro dobře navrženou aplikaci (aspoň já jsem to tak vnímal) klasická třívrstvá architektura, kterou má téměř každý vývojář tak nějak zažitou.

klasická třívrstvá architektura

Podívejme se blíže na závislosti, které máme mezi jednotlivými vrstvami. Prezentační vrstva je závislá na business vrstvě. To je legitimní závislost, protože by nám ve většině případů neměl návrh uživatelského rozhraní zásadně ovlivňovat business vrstvu. Proč? Například kvůli možnosti zavedení nové prezentační vrstvy. Závislost tímto směrem a striktní oddělení nám tedy umožňuje snadno vyvinout novou prezentační vrstvu (např. vedle webového rozhraní budeme chtít s naší aplikací komunikovat také přes webové služby).

Druhá šipka nám říká, že business vrstva je závislá na přístupu k datům. I toto má zdánlivě svou logiku…ale je opravdu způsob uložení dat ta nejdůležitější část naší aplikace? Není srdcem naší aplikace spíše business logika? Často ano! A tím jsme právě vyslovili jednu ze základních myšlenek Domain Driven Design (tím se ale zatím zaobírat nebudu). Protože je tedy business logika to nejdůležitější, proč by měla záviset na přístupu k datům, proč by se měla přizpůsobovat? Ať se radši přizpůsobí DAL! Obraťme tedy zmiňovanou závislost.

lepší třívrstvá architektura

Pokud Vás předchozí odstavec nepřesvědčil, že by mohlo být vhodné obrátit závislost mezi BL a DAL, přistupme k situaci praktičtěji.
DAL neví zhola nic o naší business vrstvě. Je to tedy nějaká víceméně univerzální knihovna pro přístup k datům. Když pracujeme třeba s nějakou relační databází a chceme udělat nějaký krutopřísně rychlý dotaz, občas se nevyhneme tomu, že jsme nuceni napsat třeba nějaký specifický SQL dotaz. A kam ho zapíšeme? No přece do business logiky! Do DAL ho zapsat nemůžeme, protože ten přece o BL nic neví – DAL je univerzální knihovna, kterou používáme klidně ve více nesouvisejících projektech. Takže abychom dosáhli lepšího výkonu, máme nějaký specifický SQL dotaz v BL – nesmrdí Vám to? Nechtělo by to přehodnotit návrh (závislosti) ?
Dalším argumentem pro obrácení závislosti může být to, že ve stávajícícm návrhu nejsme schopni jednoduše vyměnit DAL. Aplikace totiž přistupuje pouze k BL a nemá nad DAL kontrolu (ten je skryt za BL). Jednoduchá výměna DAL se nám může hodit nejen v případě migrace na jiné úložiště, ale také v případě psaní unit testů.

Jiný pohled

Pokud člověk ve firmě vyvíjí jedinou aplikaci a nemá tak potřebu sdílet DAL mezi nesouvisejícími aplikacemi, tak už možná tuto závislost obrátil, protože DAL přizpůsobil business layeru a dopsal do něj (do DALu) nějaký ten krutopřísný specifický dotaz. Je dobré si uvědomit, že tímto je zavedena závislost DALu na BL. A pokud nemáme interface DALu oddělený od implementace, tak tu máme krásnou kruhovou závislost – DAL je přizpůsoben na míru BL a BL používá konkrétní implementaci DALu. Jinými slovy, jeden bez druhého nemůže existovat a z jistého pohledu máme provázánou business vrstvu a konkrétní přístup k datům.

Obraťme tedy závislost mezi BL a DAL (který jsem přejmenoval na Persistence) a koukněme se, co z toho pro nás vyplývá – především to, že si BL může diktovat, jak má vypadat vrstva Persistence. V praxi to znamená, že Persistence nyní nemusí být (jako DAL) obecná knihovna pro přístup k datům (ale může z takové knihovny vycházet!), ale je to knihovna, která posluhuje BL a přizpůsobuje se jí. Rozhraní IPersistence tedy může vypadat třeba nějak takto:

public interface IBlogPersistence
{
  IEnumerable<Article> GetArticles();
  void AddArticle(Article article);
  void UpdateArticle(Article article);
  void RemoveArticle(Article article);
  IEnumerable<Comment> GetArticleComments(Article article);
  IEnumerable<Article> GetArticlesWithComments();
}

Jak vidno, toto není rozhraní univerzální knihovny pro přístup k datům. Nějaká knihovna pro přístup k datům (třeba Entity Framework, Linq2Sql, NHibernate, …) ale může být použita pro implementaci výše uvedených metod. To už je ale implementační detail, který je skryt za rozhraním.
Je zřejmé, že nyní můžeme ve vrstvě Persistence pěkně tunit třeba svoje SQL dotazy bez toho, aniž bychom si zašpinili BL. BL totiž zná pouze interface vrstvy Persistence.
Jedním z prvků SOLIDní architektury je Open/closed princip (btw. verze v katalánštině :)), který říká, že rozhraní by mělo být otevřené k rozšíření ale uzavřené ke změnám. Pokud je tedy předpoklad, že se budou požadavky na Persistence měnit a museli bychom tedy měnit interface IPersistence (přidávat metody), může být vhodné použít nějaký přístup, který nám umožní psát další dotazy proti Persistence aniž bychom měnili jeho interface. Toto řeší tzv. doménové dotazy, což je tedy způsob, jak se dostatečně obecně dotazovat do Persistence (jak mu předat dotaz). Když chceme implementovat doménové dotazy, můžeme vytvořit třeba množinu tříd, které pro nás dostatečně popíšou požadované dotazy. Zde může být dobrým nápadem použít něco již existujícího, třeba Linq. Ano, mířím tím k tomu, že dobrou výchozí implementací Persistence může být DataContext z Linq2Sql nebo ObjectContext z Entity Frameworku. Avšak mysleme stále na to, že tyto konkrétní frameworky jsou jen implementační detail, který je skryt kdesi v hloubi implementace IPersistence.
Jen bych dodal, že Linqem se nikterak neomezujeme jen na relační databáze nebo dokonce jen na Ms Sql – pomocí Linqu se lze dotazovat do CSV souborů, do leckterých NOSQL databází nebo do čeho chceme – nikdo nám nebrání nepsat si vlastní Linq provider. Omezujeme se tedy jen na to, že persistentní vrstva si musí umět poradit s Linqem (příp. jeho podmnožinou).

Implementace závislostí

Pro lepší pochopení zde přikládám diagram, který znázorňuje celou architekturu aplikace.

architektura aplikace

Jádrem naší aplikace jsou tedy nějaké business objekty, které se nějak persistují pomocí rozhraní IPersistence – to je jedna assembly. Důležité je, že o použité implementaci IPersistence nerozhoduje BL (protože ta ani žádnou implementaci nezná), ale konkrétní implementaci musíme do BL nějak dodat (injektovat) při použití z aplikace – např. přes parametr konstruktoru.
Dále je třeba rozhraní IPersistence někde implementovat. Můžeme mít třeba dvě implementace (ve dvou různých assembly) – MsSqlPersistence (pracuje proti MS Sql) a MockPersistence (pro účely unit testů).

Když pak máme nějakou aplikaci (třeba webové GUI), tak si musíme v Controlleru/Presenteru vytvořit instanci BL, tzn. musíme to něj strčit (injektnout) nějakou konkrétní implementaci IPersistence a pak už můžeme s BL vesele pracovat (a BL používá nainjectovanou implementaci IPersistence).
Slovem injektnout v předchozích větách jsem se snažil naznačit, že může být velmi vhodné mít závislosti vyřešené přes Dependency Injection. Pak nám stačí při startu webové aplikace nakonfigurovat kontejner tak, aby nám pro rozhraní IPersistence vracel instanci třídy MsSqlPersistence (v unit testech obdobně) – pak se nám kontejner sám postará o to, že při vytváření instance BL nám do něj přes konstruktor nainjectuje nakonfigurovanou implementaci rozhraní IPersistence.

Závěr

Zde prezentovaná architektura aplikace jistě není ideální a nemusí být ani vhodná pro každou aplikaci, ale myslím, že je lepší než klasický třívrstvý návrh. Rozhodně by si zasloužila další vylepšení, ale to by bylo na mnoho dalších článků. Proto tento článek neberte jako dogma a finální řešení, jak se mají dělat aplikace – berte ho jako návod na možné řešení problémů, se kterými se můžete ve Vaší aplikaci setkat.
V případných dalších článcích by se rád posunul v návrhu dále, konkrétně trošku směrem k DDD. Úmyslně jsem tu nechtěl hned vybalit DDD, protože se jedná o poměrně rozsáhlou záležitost plnou nových pojmů a myšlenek, které jsem ještě ne zcela vstřebal a přijal za své (co jsem četl, tak 3 roky studia a aplikace DDD na 3 projektech je dobrý základ pro základní pochopení DDD) a proto si netroufám v tomto směru zatím dále edukovat. Cílem článku je pouze ukázat čtenáři jinou cestu a trošku se zamyslet nad dogmatem klasické třívrstvé architektury aplikace (která nemusí být v každé situaci vhodná).

http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js
http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCSharp.js

if (SyntaxHighlighter) {
SyntaxHighlighter.all();
}

2 thoughts on “Architektura Modelu

  1. To je pěkné, tak mě ale napadá, že pokud nenahradíme i to GetArticleComments, obecně metody vracející něco jiného než kolekci Article, tak se stejně nevyhneme porušení open/close, třeba přibudou seriály a budeme potřebovat getSeries(Article article) a musíme rozšířit interface.

    To se mi líbí

  2. Ano. IMHO je to o tom, do jaké míry chceme mít interface univerzální a znovupoužitelný, tedy nezávislý na BL. Když ho uděláme úplně nezávislý na BL, tak to vede zpět na klasickou třívrstvou architekturu (a to nemusí být vůbec špatně) a de facto na místě DALu můžeme použít nějaký obecný ORM framework, kterému stačí existence třídy za běhu programu k tomu, aby ji dokázal persistovat.

    Další otázkou návrhu persistenčního interfacu a O/C je to, zda nemít pro každou entitu (v řeči DDD Aggregate Root) samostatný persistenční interface…

    To se mi líbí

Zanechat odpověď

Vyplňte detaily níže nebo klikněte na ikonu pro přihlášení:

Logo WordPress.com

Komentujete pomocí vašeho WordPress.com účtu. Odhlásit /  Změnit )

Facebook photo

Komentujete pomocí vašeho Facebook účtu. Odhlásit /  Změnit )

Připojování k %s

Tento web používá Akismet na redukci spamu. Zjistěte více o tom, jak jsou data z komentářů zpracovávána.