Jak navrhnout vrstvy ASP.NET MVC aplikace   zodpovězená otázka

Architektura

Zdravím,

řeším jak rozvrhnout architekturu ASP.NET MVC aplikace a nějak se nemůžu dopátrat správného konce:

1.) ASP.NET MVC projekt má závislost pouze na jednom jediném projektu (říkejme mu třeba BusinessLayer), který skrz nějaké repository vrací do UI modely, které jsou 1:1 ke konkrétní stránce. Nic víc.

2.) BusinessLayer je projekt, který pracuje s daty, provádí nad nimi potřebné operace a vrací do UI naplněný model. Sám si tahá data z poslední vrstvy, které říkám DataLayer.

3.) DataLayer je vrstva nejnižší úrovně, který je vlastně EF Code First.

Co se nemůžu rozhodnout, tak jak vyřešit získávání dat z DataLayer (nejnižší vrstvy) v rámci BusinessLayer. Vidím dvě možnosti:

A) v DataLayer mám generický context, který vystavuje Linq rozhraní, skrz které se mohu dotazovat do databáze přímo v BusinessLayer, kde se dotážu pro data, poskládám si je a vrátím model do UI.

B) v DataLayer mám generický context, který ale nevystavím. Místo toho jej pro každou entitu podědím a do BusinessLayer vystavím pouze tyto repository, ve kterých budu mít přichystány už konkrétní metody typu: GetOrdersForLastMonth().

Řekl bych, že metoda B je výrazně čistší, ale znamená více práce a ve výsledku to vede k tomu, že pro každou entitu musím vytvářet (a registrovat v IoC) vlastní repository, ve kterém jsou dvě tři metody.

Díky za Váš pohled na věc.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Čekal bych, že BusinessLayer by měla být schopná přistupovat na data naprosto libovolně jak bude potřeba. Metoda GetOrdersForLastMonth() v řešení B působí, že v sobě již právě obsahuje tu logiku, co má řešit až BusinessLayer (jestli to chápu správně, že metoda GetOrdersForLastMonth() by byla již v DataLayer).

Z tohoto pohledu je správně A.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Souhlas. DAL by měla být co nejobecnější a měla by řešit jen přístup k datům, i když ono to občas celkem splývá.

Já to řeším tak, že DAL je samotný Entity Frameworkový model a entity. Nad tím je Business Layer, která implementuje repozitáře a query objekty (query objekt vrací IQueryable, má funkce pro stránkování, řazení a filtrování a jinak než přes něj se nedá do databáze dotazovat - repozitář umí jen GetById, Insert, Update a Delete). Nad těmito repozitáři a query objekty sedí fasády, což dávám typicky do samostatného projektu. A teprve nad tím bdí webová aplikace, v případě MVC budou controllery volat funkce z těch fasád. Fasády jednak trochu učešou použití věcí z business vrstvy, typicky se tam řeší i validace a pokud jsou oddělené, snadno se dají vypublikovat jako web service pro případ, že byste časem dělal i mobilní aplikaci nebo tak něco. Někdo ty fasády považuje za součást business layer, já je vnímám jako samostatnou vrstvu, každopádně controllery v MVC by například neměly používat přímo repozitáře.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Každý typ projektu si vyžaduje trochu jiný přístup. Mě osobně se osvědčilo u menších a středních projektů (u menších je to trochu zbytečně, ale naučil jsem se takhle psát rychle a často jsem z toho těžil při následném rozšiřování):

- mít EF, většinou Code First - to se dá považovat z DL

- nad tím BL / služby pro práci s daty a BO reprezentující data, popřípadě objekty pohledů se sumáři pro výpisy do míst, kde je potřeba získat hodně dat a aktivace BO by byla pomalá

- nad tím UI facade a ViewModely

- nad tím controllery a šablony

Jen dvě poznámky:

- repozitáře ve smyslu "insert/update/get/delete" považuji v době Entity Frameworku zbytečný, většinou je potřeba stejně pracovat s komplikovanějšími dotazy, přidávání objektů do kolekcí přes agregátní root (order.OrderLines a podobně)

- navíc vystavovat IQueryable do vyšších vrstev nemám rád - obvykle řídám skládání dotazů v jednom objektu - rád mám dopředu jasné jak se budu do DB dotazovat, dává mi to možnost rychle optimalizovat dotazy v případě neoptimálních SQL dotazů do DB a zmenšuje to počet míst úprav při rozšiřování databáze (například objednávka je dokončená když je zaplacená a nyní i když není stornovaná). Navíc to vývojáře neláká dělat datovou vrstvu z prezentační vrstvy.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Jen poznámka k těm repozitářům - podle mě smysl mají, protože jednak tam řeším např. automatické nastavování příznaků CreatedDate při insertu, případně místo mazání entity jen nastavuju DeletedDate atd. Anebo můžu přepsat třeba update, aby ty entity třeba verzovalo.

Navíc moje repozitáře vyhazují i události, na které mám pak napíchnuté určité obsluhy, typicky mazání cache nebo odeslání nějakého e-mailu, logování atd. Repozitáře mám obecně jen pro agregační rooty a většinou na to nestačí generická obecná varianta, ta je opravdu skoro k ničemu - používám ji jako bázovou třídu, ale pro každou důležitější entitu tam jsou nějaké customizace.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Tak tohle jsou podle mě věci, které má řešit BL a ne DL.

Navíc jak připojuješ metody repozitáře na přidávání a odebírání objektů z kolekcí uvnitř jiných entit? Například order.OrderLines

nahlásit spamnahlásit spam 0 odpovědětodpovědět

No já mám repozitáře v BL. :-) Query objekty taky. Já na úrovni repozitářů a queries dost často mívám i oprávnění, což už mi přijde jako věc BL. DAL se stará opravdu jen o to, jak dostat data ze SQL databáze do objektu, nic víc.

V repozitářích mám defaultní LoadById s defaultní implementací (ty OrderLines by se tam asi includovaly vždy, ale záleží na aplikaci). Pokud bych chtěl jiné kombinace includů, tak bude druhá metoda LoadByIdWithOrderLines nebo tak něco.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Tak pokud to bereš takhle, tak jen každý mluvíme o něčem jiném. Já myslel běžné repozitáře zapouzdřující přístup do databáze k jednotlivým entitám na úrovni DL, které měli velký smysl zvlášť v době, kdy neexistoval Entity Framework a nebo pokud nepíšeš aplikaci, u které je pravděpodobná změna toho uložiště.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Je možné, že je zneužívám na to, na co nebyly původně zamýšleny, ale já si obecně ty návrhové vzory ohýbám tomu, co zrovna potřebuju a co se mi osvědčilo.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Jediný problém je v tom, že pojmenování pak mate. Vidět někde nějaký vzor a použít to "nějak podobně" a pojmenovat to stejně pak většinou vede ke zmatení. A to je snad jediný důvod, proč podle mě má smysl se vzorům věnovat. Jinak je možné i bez znalosti vzorů kód vymyslet úplně stejně kvalitní. Stejně je to většinou kombinace řady nejrůznějších vzorů v nejrůznějších podobách.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

No ono je otázkou, jak je přesně ten návrhový vzor repository definován, mě přijde, že jeho pravidla úplně neporušuju, akorát ho nepoužívám na ten učebnicový příklad.

Ten můj repozitář podle mě dělá to samé, akorát interně používá EF a občas implementuje kus business logiky (i když záleží, jestli mazání nastavením příznaku nebo verzování změn je vůbec business logika).

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Já bych opustil tuhle diskusi, protože to začíná zavánět akademickým mimráním v termínech :-)

Jen pro porovnání - já takové triggery na objektech řeším místo repozitáře pomocí interfaců implementovaných přímo code-first objekty (IOnSaving atp.), které očekává moje rozšíření EF kontextu a postará se o jejich vyvolání při ukládání. Díky tomu mohu tuto vrstvu vynechat.

Hlavní myšlenka byla, že používat repozitář tak, jak je popsán například na http://martinfowler.com/eaaCatalog/repos... nad Entity Frameworkem sám o sobě (a to bez rozšiřování například o zachytávání ukládání změn jako popisuješ), je obvykle zbytečné, protože u EF taková vrstva obvykle nedělá nic.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Zdravím, mám jen dotaz k tomu [CreatedDate] při insertu a verzování záznamů (příp. trackování změn v db, tvorba journalu). Osobně mívám tento model implementovaný přímo v databázi pomocí default hodnot, constraints, rules a triggerů. A např. identifikaci uživatele mám uloženou přímo v context_info, kterou tam při otevření předává DataContext (L2SQL) popřípadě DbContext (EF). V aplikaci se pak o toto vůbec nemusím starat. Stejně tak mám v db přímo implementaci různých výpočtů atp. (pomocí computed columns). Zajímá mě ale Váš názor, zda je to z pohledu dnešního vývoje nějaká prasárna nebo je to obhajitelné. Díky za případnou odpověď.

nahlásit spamnahlásit spam 0 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.
  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:
Heslo:

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback