\

Školení Návrhové vzory, OOP a UML


 Tuesday, 20 March 2012
Pozvánka na kurz objektových principů a návrhových vzorů – jaro 2012 a informace k dalším kurzům

Opět bych vás chtěl pozvat na kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1.


Veřejný kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1

Datum konání kurzu:  4. 6.6. 6. 2012

Místo konání:

Školící středisko Tutor

U Půjčovny 2
110 00 Praha 1

Po celý den máme k dispozici wifi připojení a samozřejmě také teplé i studené nápoje. V ceně kurzu jsou obědy v restauraci.

Podrobné informace o kurzu a možnost přihlásit se na kurz

Program kurzu
Výběr z ohlasů na kurz

Zde jsou ještě některé ohlasy z twitteru na  kurzy, které proběhly na podzim roku 2011:
https://twitter.com/#!/AugiCZ/status/129271721512538112

https://twitter.com/#!/topascz/status/129228333991989248

https://twitter.com/#!/petrkucera/statuses/129474672575250432


FAQ - často kladené dotazy ke kurzům


Tento rok se na jaře uskuteční pouze výše popsaný kurz. Dva další kurzy, Školení Základy objektově orientovaného návrhu a vývoje (UML 0) a Pokročilé návrhové vzory a objektové principy 2, proběhnou na podzim a můžete se na ně také  předběžně hlásit. Důvodem, proč tyto kurzy neproběhnou na jaře, je moje vytížení dalšími projekty.

Pro jistotu připomenu, že všechny kurzy lze také objednávat v “inhouse” variantě, kdy kurz proběhne ve vaší firmě v termínech, na kterých se spolu domluvíme, a za podmínek, které s vámi ráda dohodne Petra Steinová. V inhouse variantě je také samozřejmě možné zcela upravit program kurzu  a věnovat se jen “specialitkám” a “špekům”, které v aplikaci řešíte.

Budu se těšit na záludné dotazy na kurzu.Smile



Tuesday, 20 March 2012 09:28:04 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  Kurzy UML a OOP | Návrhové vzory | UML


 Monday, 12 March 2012
Lazy loading (zpožděné nahrávání) objektů do kolekce i ve starší aplikaci s využitím dynamické proxy

 

Jestliže používáte i přes jeho nezralost Entity Framework, nebo jste si zvolili jiné ORM, které zvládá “lazy loading”, neboli zpožděné, či chcete-li dodatečné nahrání dat do typových kolekcí, možná jste přemýšleli, jak byste stejnou  službu napsali ve starší aplikaci, která žádné ORM nepoužívá, nebo v hybridní aplikaci, pod kterou si představuju aplikaci, jejíž starší moduly ORM nepoužívají, ale novější moduly již s přístupem přes ORM počítají. I bez ORM byste ale často v aplikaci rádi využívali některé vychytávky, které s sebou přináší ORM. Dnes chci ukázat, že zpožděné nahrávání kolekcí není žádná magie, která by bez ORM byla v aplikaci zapovězena. Když budete mít zájem, můžeme v dalších článcích probrat například i automatickou detekci změn vlastností na objektech a s tím související ukládání objektů i odvolávání proběhlých změn, jestliže zákazník nechce změny uložit a třeba na formuláři stiskne po deseti minutách zuřivé editace objektu a po několika masivních business transakcích, které pozmění desítky objektů najednou, tlačítko Storno.

Co si představit pod zpožděným nahráním  objektů v kolekci? Můžeme vyjít z již zlidovělé třídy Objednávka (Order), která má kolekci svých položek (kolekce Items). Místo nahrání všech položek objednávky z databáze ihned po vytvoření instance objednávky, odložíme nahrání položek až na dobu, kdy budou v aplikaci poprvé potřeba. Před klienty třídy Order ale tuto optimalizaci skrýváme tak, že kolekci Items naplníme položkami teprve při prvním přístupu ke kolekci přes veřejné rozhraní třídy Order.

Jde sice o triviální kód, kdy v get akcesoru kontrolujeme bool příznak m_itemsIsLoaded (byly položky nahrány?) a při prvním přístupu ke kolekci zavoláme metodu loadItems, ale představte si, že tento kód u starší aplikace zběsile doplňujete ke každé kolekci v každé třídě, kde teď stojíte o zpožděné nahrávání. Čitelnost kódu je citelně snížena a chrabří obhájci principu jedné odpovědnosti třídy (SRP) právě dopisují Kladivo na heretiky S.O.L.I.Dní víry pravé a připravují v zájmu lepší veřejné vývojářské morálky autodafé účtu dotyčného vývojáře na všech významných sociálních sítích.

Můžeme se rozhodnout, že nebudeme zatěžovat zpožděným nahráváním kolekcí přímo třídu Objednávka, ale že vytvoříme proxy třídu, do které odpovědnost za tuto “infrastrukturní službu” vložíme. Proxy objekt je, jak známo, objekt, který má z hlediska klienta stejné rozhraní jako původní objekt a klient si není vědom, že pracuje s instancí zástupce (surogátem) třídy Order, a ne s originální třídou Order.

Samotná třída Objednávka není nijak zatěžována znalostí, že její kolekce Items je nahrána až při prvním přístupu ke kolekci. Zpožděné nahrávání zvládne ale potomek třídy Order, třída OrderProxy, ve které je podobný kód, který se původně nacházel v objednávce. A protože platí, že potomek nějaké třídy může být v aplikaci používán na všech místech, kde je očekáván předek, můžeme například z repozitáře/identitní mapy objektů začít ihned vydávat klientům instance OrderProxy místo originální třídy Order.

Odpovědnosti už jsme rozdělili lépe, protože třída Order není zatěžována zpožděným nahráváním kolekce a jediným důvodem existence třídy OrderProxy je právě zpožděné nahrávání. Přesto stále platí, že budeme do úmoru psát další a další třídy Proxy, u nichž  jediná kreativní vývojářská činnost spočívá v pojmenování bool proměnné, která nám sděluje, jestli kolekce byla, nebo nebyla nahrána. Je možné si práci zjednodušit tím, že například vytvoříme T4 šablonu, která proxy třídy vygeneruje, ale my se dnes zaměříme na to, jak vytvořit proxy třídu, aniž bychom museli psát proxy ručně nebo spoléhat na T4 šablony.

Použijeme takzvanou dynamickou proxy, kterou si lze představit jako nástroj, kterému řekneme, co má proxy dělat, a on pro každou třídu bez ohledu na unikátní rozhraní každé třídy sám vygeneruje proxy, která pro tuto třídu implementuje námi vyžadované chování. Slovo “dynamická” u proxy vyjadřuje hlavně to, že jde o proxy generovanou automaticky za běhu aplikace! V našem konkrétním scénáři se zpožděným nahráváním objektů vytvoříme i pro aplikaci, která má v business vrstvě stovky i  tisíce tříd jen jednu další třídu, která představuje “deskriptor” pro každou proxy zajišťující  zpožděné nahrávání kolekcí, přičemž platí, že tento “deskriptor“  bude schopen obsloužit všechny kolekce ve všech třídách. Sice se pro každou třídu vytvoří unikátní proxy (potomek originální třídy), ale tato proxy se bude odkazovat na obecný scénář zpožděného nahrávání v našem “deskriptoru”. Při čtení dalších částí článku mějte na paměti, že kód dělá stále to samé, jako námi vytvořená třída OrderProxy výše, jen jsme kód zobecnili tak, abychom si dokázali vynutit zpožděné nahrávání kolekcí ve všech třídách. Vedlejším a nepříjemným důsledkem tohoto zobecnění, jak je tomu asi u každé abstrakce,  je samozřejmě snížená schopnost jen po letmém prolétnutí infrastrukturního kódu očima poznat, oč přesně usilujeme. Výhodou však bude to, že náš deskriptor oddálí zmiňované autodafé vývojáře už jen tím, že dodržuje princip DRY-Don't repeat yourself – místo psaní jen mírně obměněného kódu v každé konkrétní proxy jednorázově vyjádříme náš záměr zpožděně nahrávat kolekce v “deskriptoru” pro generování dynamické proxy.

Mějme třídy Customer, Order a OrderItem. Zajímá nás hlavně to, že třída Customer má kolekci objednávek a třída Order kolekci položek objednávky. Pro všechny kolekce chceme doplnit zpožděné nahrání kolekcí.
Všimněte si také toho, že vlastnosti s kolekcemi jsou virtuální. Stále platí, že dynamická proxy je potomkem naší třídy a musí být schopna přepsat implementaci a doplnit kód pro zpožděné nahrání kolekce stejně, jako jsme přepisovali get akcesor u “manuální” proxy výše.

I když je mnohem zábavnější napsat si podporu pro dynamicky generované proxy sám, ne vždy bychom měli vynalézat na projektu kolo, zvláště když jsme v časovém presu  a navíc pod drábovou knutou nudných projektových manažerů surově lámajících naše vývojářská křídla Smile , a alespoň v tomto článku použijeme výborný a ověřený nástroj pro generování dynamických proxy z projektu Castle. Nejjednodušší způsob přidání knihovny pro generování dynamických proxy spočívá v instalaci přes NuGet. V Powershell konzoli ve Visual studiu zadejte příkaz.

Vytvoření dynamické proxy v Castlu je kupodivu otázkou napsání  jednoho řádku kódu. My služby Castlu zapouzdříme do naší vlastní třídy ProxyEngine.

Do konstruktoru třídy ProxyEngine dostáváme instanci třídy SimpleObjectFactory, kterou si můžete prozatím zjednodušeně představit jako velmi jednoduchou generickou továrnu na výrobu business objektů, která zároveň funguje jako identitní mapa. Její kompletní výpis naleznete níže v tomto článku.

Hlavní je pro nás metoda AddDefaultProxy, která dostává jako první argument typ, pro který má být vytvořena proxy. Tedy předáte-li typový deskriptor objednávky, metoda by měla vyrobit OrderProxy. Druhým argumentem jsou argumenty, které mají být předány konstruktoru naší třídy. Jestliže objednávka vyžaduje v konstruktoru odkaz na své id, vygenerovaná proxy třída garantuje, že jí id bude do konstruktoru předáno.

Teď přichází zajímavá část – vytvoření proxy:

Proměnná m_proxyGenerator je instancí třídy ProxyEngine z Castlu, která představuje výkonné jádro pro generování proxy. Prvním argumentem metody proxyGenerator.CreateClassProxy je typ, pro který chceme proxy vytvořit. Druhý argument typu ProxyGenerationOptions jsou různé volby, které dovolují jemně řídit, jak se bude vytvořená proxy chovat. My zatím potřebujeme jen sdělit, které metody originální (ne proxy) třídy chceme v proxy “přepsat”. Proto jsou ProxyGenerationOptions inicializovány hned v konstruktoru třídy ProxyEngine a je jim předána instance třídy ProxyGenerationHook, která, jak ihned ve výpise uvidíme, vybírá kolekce, u kterých má být podporováno zpožděné nahrání objektů.

Rozhraní IProxyGenerationHook je rozhraní Castlu. Metoda ShouldInterceptMethod z tohoto rozhraní je metoda, kterou Castle používá k rozhodnutí, jaké metody a vlastnosti mají být “přepsány” v dynamické proxy. Třída ProxyGenerationHook v metodě ShouldInterceptMethod říká  - tedy vrací true - , že chceme zachytit všechny get akcesory ( methodInfo.IsSpecialName&& methodInfo.Name.StartsWith(GET_METHOD_NAME_PREFIX), jejichž návratovou hodnotou je kolekce. Přesněji řečeno každá kolekce podporující generické rozhraní ICollection<T> (methodInfo.ReturnType.GetInterface(COLLECTION_NAME) != null). Jiných metod ani vlastností si v tomto článku u tříd  nevšímáme, a proto pro ně z metody ShouldInterceptMethod vrátíme false. Zajímavou metodou v rozhraní IProxyGenerationHook je i metoda NonVirtualMemberNotification, pomocí níž nás Castle informuje, že v originální třídě je nevirtuální metoda – my metodu NonVirtualMemberNotification nevyužíváme, ale mohli bychom do ní snadno doplnit kód, který vyhodí výjimku, jestliže jste Castlem notifikováni, že existuje nevirtuální vlastnost vracející ICollection<T>, protože vaše firemní konvence vyžadují, aby všechny kolekce podporovaly zpožděné nahrávání kolekce.

Nyní jsme již Castlu sdělili, že máme zájem “přepsat” get akcesory kolekcí, ale stále Castle neví,  jakou logiku má do těchto get akcesorů doplnit. Vraťme se k metodě CreateClassProxy. Třetí argument je zřejmý, předáváme argumenty, se kterými má být zavolán konstruktor naší originální třídy. Posledním argumentem metody CreateClassProxy je objekt, který nás zajímá nejvíce – jedná se o tzv. interceptora, který bude použit vždy, když je na proxy použita  metoda/vlastnost, kterou chceme “přepsat”. Stále píšeme obecné řešení zpožděného nahrávání kolekcí, a proto metodě CreateClassProxy předáme interceptora s výmluvným názvem LazyLoadInterceptor. LazyLoadInterceptor je ten “zázračný” typ, který jsem výše popisoval jako obecný deskriptor funkcí, které musí podporovat dynamická proxy pro instanci z každé třídy v business vrstvě.

Náš LazyLoadInterceptor, stejně jako každý jiný interceptor, musí podporovat rozhraní  IInterceptor z Castlu. Rozhraní IInterceptor má jedinou metodu Intercept, kterou Castle zavolá vždy, když je volána metoda/vlastnost, kterou chceme “přepsat”.

Metodě Intercept je předán objekt Invocation, který nese základní informace o volané metodě.Kromě dalších vlastností je vhodné si zapamatovat, že v invocation.Method naleznete objekt MethodInfo  (deskriptor metody) a  v InvocationTarget zase konkrétní instanci, na které je metoda volána. Zdůrazním, že touto konkrétní instancí je v našem případě (dynamický) proxy objekt, ne instance originální třídy.

Zkusme si scénář v metodě Intercept projít. Mějme na paměti, že i když ten kód může vypadat na první pohled děsivě, neřeší nic jiného než ručně napsaná proxy výše. Zkusme se v našem popisu pro názornost zaměřit na konkrétní proxy objektu reprezentujícího zákazníka Josefa Nováka v momentě, kdy je poprvé přistoupeno k jeho kolekci Orders (seznam objednávek), i když kód v LazyLoadInterceptoru funguje analogicky ve všech dalších proxy business tříd v systému.

  • Metoda Intercept nejprve na předaném objektu invocation volá metodu Proceed.  Metoda Proceed vyvolá get akcesor originálního objektu a návratovou hodnotu (při prvním volání prázdnou typovou kolekci objednávek) nalezneme ve vlastnosti invocation.ReturnValue. Proč voláme nejprve invocation.Proceed? Protože potřebujeme v interceptoru kolekci, do které u zákazníka můžeme nahrát objednávky, a tuto kolekci stále spravuje instance originální třídy, jak si můžete ověřit ve výpisu třídy Customer.
  • Jestliže se nejedná o první volání metody, nic neděláme, protože kolekce už musí být naplněna. V proměnné m_inspectedMethods máme názvy vlastností, které jsme již u daného objektu zpracovali.
  • Jestliže invocation.ReturnValue je null, opět nic dalšího neděláme. Nemáme žádnou kolekci, do které bychom mohli nahrát objednávky.
  • Do kolekce m_inspectedMethods přidáme název aktuální vlastnosti (Orders), protože jsme ji již začali zpracovávat.
    m_inspectedMethods.Add(invocation.Method.Name);

  • Nejprve potřebujeme zjistit, z jaké třídy pocházejí objekty,  které budeme do kolekce, jejíž data nahráváme, přidávat. U objektu zákazník a kolekce Orders půjde samozřejmě o objekty z třídy Objednávka.
    Type collectionItemType = invocation.Method.ReturnType.GetInterface(COLLECTION_INTERFACE_NAME).GetGenericArguments()[0];
  • Dohledáme třídu z databázové vrstvy, která nám bude schopna vrátit seznam objednávek pro daného zákazníka.
    Object dbComponent = findDbComponent(collectionItemType);
    Pro účely článku je zvolena jednoduchá jmenná konvence -  rozhraní pro přístup k databázi se jmenují vždy I<Název třídy>DbComponent. Pro objednávku tedy hledáme typ IOrderDbComponent. Pokud db komponentu nenalezneme, nic dalšího nemůžeme dělat.
  • V nalezené db komponentě musíme najít metodu, která nám vrátí záznamy pro všechny objednávky zákazníka Josefa Nováka. 
    MethodInfo methodInfo = getDbCollectionMethodInfo(dbComponent, invocation.TargetType, collectionItemType);
    Opět je zvolená jmenná konvence, kdy metoda má tvar Get{TypeInCollection}RecordsBy{ParentType}Id" a přijímá jeden argument typu int . V našem scénáři hledáme tedy na db komponentě metodu GetOrderRecordsByCustomerId, která přijímá id “rodičovského” zákazníka. V dalších článcích bych rád ukázal, jak se bez této i dalších dále zmíněných jmenných konvence obejdeme a budeme moci nakonfigurovat zpožděné nahrávání kolekcí přes jakkoli nazvané třídy a metody.
    Stejně jako v předchozím bodě platí, že nenalezneme-li vyhovující metodu, nic dalšího nemůžeme dělat.
  • Dále u objektu zákazník získáme hodnotu vlastnosti Id, kterou potřebujeme pro vyvolání metody na db komponentě nalezené v předchozím odstavci
      int? objectId = getTargetObjectId(invocation.InvocationTarget);
  • Zavoláme metodu GetOrderRecordsByCustomerId. Návratovou hodnotou je objekt DataTable, který v našem scénáři obsahuje záznamy všech objednávek patřících Josefu Novákovi.
    DataTable retValues = methodInfo.Invoke(dbComponent, new object[] {objectId}) as DataTable;
  • Přes pomocnou třídu SimpleObjectFactory, kterou LazyLoadInterceptor vyžaduje v konstruktoru, vytvoříme proxy objekty Objednávek a přidáme je do kolekce Orders zákazníka.
    var targetCollection = invocation.ReturnValue;
    addItemsToCollection(targetCollection, collectionItemType, retValues, invocation);
    Navíc se u každé vytvořené objednávky pokusíme nastavit odkaz na  “rodičovského zákazníka”, přesněji řečeno na proxy zákazníka. Opět je zvolena jmenná konvence, kdy objekt Order musí obsahovat vlastnost nazvanou Customer, jinak k nastavení “rodiče” nejde. Jak jsem již psal výše, v dalších článcích bychom si měli ukázat, jak tyto výchozí jmenné konvence rozšíříme a dovolíme i jejich úplné nahrazení.
  • Hotovo, kolekce Orders u zákazníka Josefa Nováka je naplněna proxy objekty třídy Order a stejný scénář proběhne i při přístupu ke kolekci Items (položky objednávky) u každé objednávky.

Nyní můžeme vyzkoušet, jestli jsou proxy třídy generovány a hlavně jestli naše úsilí nebylo marné a proxy třídy podporují zpožděné nahrávání kolekce.

 

consoleProxy

Výsledkem by měl být tento výpis, ze kterého je patrné :

  • Místo originální třídy jsou používány proxy třídy.
  • Kolekce jsou naplněny, i když ve třídách Customer ani Order žádný kód pro nahrání kolekce nemáme.
  • Je naplněna kolekce Orders u zákazníka i kolekce Items u každé objednávky.

Následuje slibovaný výpis generické třídy SimpleObjectFactory. 

Třída SimpleObjectFactory podporuje rozhraní SimpleObjectFactory a při vydání objektu:

  1. Funguje jako identitní mapa, takže každý objekt je nahrán jen jednou (nyní per process, což se dá snadno změnit).
  2. Používá záměrně služby “zastaralé” db vrstvy pro nahrání dat každého objektu – vytváření objektů ale deleguje na ProxyEngine.
  3. Pokusí se nastavit hodnoty jednoduchých vlastností u vytvořených proxy objektů – jestliže se název vlastnosti shoduje s názvem sloupce v datovém zdroji, je vlastnost objektu nastavena na hodnotu sloupečku, která je uložena v řádku  vytaženém z databáze.

Co můžeme udělat dále v dalších článcích, jestliže budete mít zájem:

  1. Nebudeme spoléhat na jmenné konvence při plnění kolekci a “rodičovských” vlastností, ale dovolíme nakonfigurovat interceptora tak, abychom mohli spravovat asociace mezi třídami podle konvencí unikátních pro každý projekt, a přitom abychom nemuseli do těchto nízkoúrovňových proxy služeb moc zasahovat. Konfiguraci provedeme nejlépe pomocí fluentního API.
  2. LazyLoadInterceptor nebude používat stále dokola reflexi pro dohledání typů a metod, ale bude nalezené hodnoty cachovat.
  3. Budeme schopni podpořit i zpožděné nahrávání “rodičovských” vlastností. Prozatím je “rodičovská” vlastnost nastavena jen při nahrání kolekce – když vytáhnete z databáze jako první objednávku a sáhnete na její vlastnost Customer, vlastnost vám nyní vrátí null!
  4. Mohli bychom rozšířit proxy třídy o sledování změn vlastností a umožnit u každého objektu uložení změn nebo vrácení změn (undo).
  5. Zavedeme repozitáře (Repository), které i v hybridní aplikaci sjednotí ve vyšších vrstvách aplikace přístup k objektům, které jsou nahrány přes ORM i k objektům vytaženým z našich stávajících “old school” db/business služeb.
  6. Místo toho, abychom generovali dynamické proxy vždy po startu aplikace, umoříme jednorázově (pro většinu aplikací stejně zanedbatelnou) režii spojenou s tímto postupem vygenerováním a uložením assembly s dynamickými proxy při prvním spuštění nové verze aplikace. Při dalším spuštění aplikace se již použijí proxy ve vygenerované assembly.

Pro dnešek toho bylo ale myslím dost. Snad jen dodám, že jsem se v tomto článku chtěl vyhnout různým buzzwordům, ale fajnšmekrům potvrdím, co asi sami tuší, že jsme v tomto článku zavítali do hájemství AOP - aspektově orientovaného programování.

Celý projekt si můžete stáhnout, nejlépe přes Mercurial (hg). Součástí zdrojových kódů je i jednoduchá třída napodobující rozhraní tradičních “db komponent” pro přístup do databáze a zpřístupňující data v Datasetu.



Monday, 12 March 2012 14:06:49 (Central Europe Standard Time, UTC+01:00)       
Comments [11]  .NET Framework | Entity Framework | Návrhové vzory