pondělí, března 16, 2009

Trochu teorie kolem DSL

Doufám, že vás v mém posledním postu moc nevystrašilo slovní spojení "Domain-Specific Language". Je to jen další buzzword: zní to sofistikovaně, ale ve skutečnosti to není nic nového. Laskavý čtenář, kterému rostou víc vousy než vlasy, si možná vzpomene na minijazyky v unixu, což není nic jiného než DSL - malý jazyk vymyšlený pro konkrétní účel.

Dnes se DSL nejčastěji objevují ve formě konfiguračních souborů. Například *.hbm.xml soubory v NHibernate jsou zapsány jazykem speciálně vytvořeným pro problémovou oblast (=doménu), kterou je v tomto případě mapování mezi databází a objekty. Nebo obyčejný web.config v asp.net aplikaci: To je jazyk specifický pro konfiguraci .NET website, kde je nutné určit které moduly a handlery budou pro který typ souboru použity:-) Takže s nějakým DSL se setkal každý programátor, jen o tom možná neuvažoval jako o DSL.

Doporučuju vám tuto prezentaci, kde Martin Fowler vysvětluje, co si představuje pod pojmem DSL. Všimněte si, že říká že hranice co už je DSL a co ještě není je neostrá. Dokonce i C# nebo java kód může být chápán jako DSL, kde forma zápisu je shodná s programovacím prostředím. V takovém případě se jedná o interní DSL, který lze přímo spustit.

Oproti tomu externí DSL jazyky mají svůj vlastní způsob zápisu, nezávislý na existujících programovacích jazycích. To přináší větší svobodu v definici syntaxe jazyka, na druhou stranu musíme kód nějak parsovat a interpretovat sami. To je natolik složité, že ve většině případů by komplexita parseru a generátoru převážila nad zisky z používání DSL jazyka, což je mimochodem důvod, proč mají tvůrci dnešních DSL tak rádi XML, XSD a XSLT. Další nemalé úsilí by bylo zapotřebí k tomu, aby se "user experience" při používání DSL jazyka přiblížila moderním IDE: Kdo jednou zkusil intellisense ve Visual Studiu a průběžnou analýzu kódu s ReSharperem, těžko se bude chtít vracet k plaintextu.

Když už máme náš nový jazyk definovaný a umíme ho parsovat, je nutné se rozhodnout jak ho budeme interpretovat. Můžeme z DSL jazyka generovat zdrojové kódy v jiných (existujících) jazycích, kompilovat je a distribuovat náš program jako binární knihovny. Druhou variantou je napsat "framework", který bude náš DSL kód interpretovat za běhu a distribuovat tento framework s výchozí podobou DSL kódů, které si koncový klient může upravit podle svého. Typickým příkladem prvního přístupu jsou programy pro modelování v UML, v druhém případě pak konfigurační soubory nebo skriptovací jazyky.

Generování kódu nám jako autorům programu dává do rukou mocnou techniku: Možnost následných úprav vygenerovaného kódu v místě různých "extension points", jako jsou partial třídy nebo double-derived pattern (vygenerujeme base třídu s virtuálními metodami a vlastnostmi a prázdného partial potomka). Díky tomu náš DSL jazyk nemusí řešit všechny krajní varianty. Když to obrátíme můžeme říct, že pokud se rozhodnete váš DSL interpretovat až za běhu, musíte vaše řešení zcela popsat pomocí DSL - včetně všech výjimek a minoritních úkazů. Dostáváte se tak do "customizační pasti", kdy musíte napsat kompletní framework dřív, než budete moci váš DSL použít. Generovat kód je jednodušší, protože se stačí zaměřit na bežné jevy a speciální případy ošetřit v C# nebo jiném vhodném jazyce. Postupně můžeme přidávat počet situací, které jsou v našem DSL řešeny, nebo dokonce můžeme časem implementovat framework pro interpretaci za běhu, ale nebrání nám to začít v malém.

A nyní, milé děti, můžeme plně ocenit sílu language workbenches jako je DSL Tools pro Visual Studio: Jsou to externí jazyky, kde máme syntaxi plně pod kontrolou, máme k disposici parser a generátor, prvotřídní integraci do vývojového prostředí, a navíc grafický editor s předpřipraveným systémem validace modelu, který můžeme upravovat dle libosti. Programátorský Eden. Jediné co chybí je standardní způsob slučování více modelů do jednoho, ale to lze poměrně jednoduše obejít. O tom ale jindy.

sobota, března 14, 2009

Model Driven Development v praxi

Přeskočit přímo na video

Kolem modelování v software už byla napsána a řečena spousta vznešených slov (třeba zde). Podívejme se proto na jednoduchý způsob, jak ho implementovat v praxi.

Příklad


Máme webovou aplikaci (např. online shop), která ukládá data do databáze. Aplikační logika je implementována jako domain model, takže pro každou "entitu" v reálném světě (klient, produkt, objednávka atd.) existuje v aplikační vrstvě třída. Objekty jsou do databáze mapovány pomocí NHibernate.

A nyní si představte, že do objednávky budete chtít přidat další údaj, kontaktní telefon. Maximální počet znaků je 9. Musíte editovat webovou stránku s formulářem objednávky, musíte přidat javascriptový validátor, přidat property Phone do třídy Order a rozšířit její validační metodu, do mapovacího souboru Order.hbm.xml doplnit <property name="Phone" type="string" length="9" not-null="true" /> a do databázové tabulky tblOrder přidat sloupec Phone NVARCHAR(20) NOT NULL. Dost práce, navíc spousta duplicity, že? A po týdnu zjistíte, že telefon se do 9-ti znaků nevejde (národní předvolby) a rozhodnete se zvětšit délku údaje na 20 znaků. Budete opět muset upravit všechna zmíněná místa, na žádné nezapomenout a neudělat překlep.

Model


Přitom se stále jedná o jeden logický koncept: Objednávka má pole telefon s délkou 20 znaků. Daleko rozumnější by proto bylo udržovat tyto definice na jednom místě ("model" naší aplikace) a ostatní "artefakty" (části kódu) generovat automaticky. Vymyslíme tedy způsob zápisu, jak model definovat, vytvoříme model naší aplikace a pak ho budeme nějak parsovat a podle nějakých šablon generovat artefakty kódu. Způsob zápisu modelu je náš doménově-specifický jazyk (DSL), který umožňuje zachycovat entity z domain modelu aplikace.

Nabízí se tři řešení, jak to udělat: Vlastní implementace, univerzální CASE nástroj, nebo použít existující "language workbench".

Vlastní implementace vypadá jednoduše: Uděláme nějaký "texťák" (céčkař) nebo "iks-em-elko" (dotneťák) a napíšeme si jednoduchou utilitku, která ho přečte a na stanovená místa vyplivne kód, v lepším případě pomocí nějakých šablon (např. XSLT). Jenže model bude mít velmi mnoho vlastností a mezi prvky modelu budou vztahy které nelze definovat jinak než pomocí nějakých "ídéček". Ze začátku jednoduché jak facka, ale až model naroste, bude velmi nepřehledný a udržovat ho bude dost pracné. Takže se uchýlíte k tomu, že ve svém volném čase budete psát další utilitky pro kontrolu modelu, prohlížení modelu s navigací po vztazích mezi entitami, editor se zvýrazňováním syntaxe atd. To vám zabírá čas který by jste měli věnovat učení se novým technologiím. Výsledek nestojí za to, veřte mi, zkusil jsem to:-) Navíc ztrácíte podporu internetové komunity, čímž se okrádáte o nejrychlejší způsob řešení problémů - gůglení.

Velmi svůdné řešení je použít nějaký CASE nástroj, nejspíš na bázi UML. Jejich nevýhoda ale je, že jsou příliš univerzální, takže 90% jejich fíčur vůbec nepoužijete, a naopak když budete potřebovat něco trochu jiného než program umožňuje, budete vymýšlet nejrůznější hacky jak to udělat, což opět znepřehledňuje model. Výsledkem je velmi vratký rovnák na vohejbák (věřte mi, také jsem se o to pokoušel). Dále se připravte na to, že tyto nástroje mají obvykle strmou učící křivku (zná někdo lepší překlad "steep learning curve"?), že budete muset zjistit jak to integrovat s vaším IDE a že každý vývojář bude potřebovat placenou kopii nástroje.

"Language workbench" je Fowlerův termín pro sadu nástrojů, které usnadňují tvorbu vlastních DSL jazyků. Plurál je na místě, pro každý účel můžete vytvořit speciální jazyk přesně na míru problému, daleko vhodnější než různě přiohnuté UML. Tyto nástroje zahrnují tvorbu jazyka, práci s modelem, generování artefaktů z modelu a integrace do IDE. Je to nejlepší z obou světů: Snadno vytvoříme jazyk přímo pro konkrétní účel včetně generování artefaktů, a přitom máme k disposici nástroje a internetovou komunitu. Navíc můžeme jazyk tvořit inkrementálně, začít skromě a postupně přidávat funkčnost podle potřeby, tam kde se ukáže že je to možné a přínosné. Tak si v našem příkladě můžeme vytvořit doménový jazyk pro definici entit a jejich vlastností, a později přidat "constraints", např. minimální a maximální hodnoty vlastnosti, a upravit šablony aby generovaly validační kód na všechny vrstvy kde je potřeba.

Osobně vím o dvou takových "workbenchích", jedna je Meta Programming System od JetBrains (stále ještě v beta), druhá DSL Tools od Microsoftu, součást Visual Studio SDK. První jsem nezkusil, ale pokud programujete v javě a používáte IDEA, asi by to stálo za pokus. Druhou používáme v praxi pro definování aplikačního domain modelu a generování SQL, HBM, domain tříd, Repository, Query a DTO tříd. Podle constraints definovaných v modelu jsme schopni nagenerovat i základní klientské validátory v javascriptu.

DSL Tools


Po zdlouhavém úvodu se konečně dostáváme k věci. Protože je na českém internetu velmi málo zdrojů o DSL Tools, pokusil jsem se natočit vlastní screencast pro začátečníky. Má vás přenést přes vstupní bariéru a předvést základní kroky, které jsou nutné k vytvoření jednoduchého DSL jazyka. Sám jsem na začátku dost tápal, tak doufám že vám to ulehčí start. Je to můj první screencast, tak buďte prosím shovívaví (ano, dělám si alibi:-). DSL Tools jsou součástí Visual Studio SDK a k vytváření jazyků je potřeba Professional edice. Pak je možné vytvořit add-in package do Visual Studia a k běžné práci s modelem by měla stačit Standard edice bez nutnosti instalovat SDK (modelovací runtime je od verze 2008 součástí Visual Studia). Zásahů do jazyka je potřeba poměrně málo, protože většinu problémů vyřešíte pouhou změnou tt šablony.

Ve videu uvidíte, jak se nejdřív definuje DSL jazyk, a jak se vyzkouší v nové instanci Visual Studia. Kód se generuje pomocí T4 a zvýraznění syntaxe poskytuje Clarius T4 Editor (plugin do Visual Studia).

Jakmile překonáte vstupní bariéru, vše půjde hladce. Na netu jsou dostupná další videa v angličtině, doporučuju také blog Olega Sycha který se věnuje generování artefaktů pomocí T4 šablon a zná spoustu fíglů. Pokud to s DSL myslíte vážně, kupte si knížku Domain-Specific Development with Visual Studio DSL Tools, to je asi nejlepší existující dokumentace.

A co mám na DSL Tools nejradši? Že prostředí, ve kterém vytváříte váš DSL jazyk, je samo doménově-specifickým jazykem pro modelování DSL jazyků:-) Jinými slovy, meta-modelování a modelování používá totožný modelovací runtime. To co vytváříte v DSL projektu v souboru DslDefinition.dsl, je model vašeho jazyka, který je následně transformován pomocí tt šablon na C# kód, přeložen, nainstalován do experimental hive Visual Studia. Stejně pak funguje váš jazyk: Umožňuje vytvořit model, transformovat na kód, zkompilovat a spustit. Znamená to také, že váš DSL jazyk můžete lehce customizovat pomocí partial a double derived tříd. Není to žádný trik, ale starý dobrý C#. Dál to taky znamená, že vše co vidíte v DslDefinition.dsl (vzhled a layout diagramu, editory vlastností, validace modelu atd.) můžete použít i ve svém jazyku. Můžete si být jistí proto, že váš model poběží na stejném runtime jako designer DSL jazyka.

Solution vytvořené ve videu: SampleDsl.zip