Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc

Język dziedzinowy w praktyce (Domain-Specific Language Tools)

Wprowadzenie

Języki dziedzinowe projektowane są po to by ułatwiać opisywanie zagadnień pewnej dobrze określonej dziedziny. W przeciwieństwie do języków ogólnych takich jak C# czy C++, języki dziedzinowe są wysoce wyspecjalizowane i ukierunkowane na konkretne zadania i problemy. Dla przykładu można zdefiniować język opisu interfejsu użytkownika, procesu biznesowego, bazy danych albo przepływu informacji. Języki takie będą do opisu problemu używały ontologii danej dziedziny. Definiując interfejs użytkownika będziemy tworzyć okna i pola tekstowe, a definiując bazę danych – tabele, klucze itd. Mając język dziedzinowy (model dziedzinowy) możemy zdefiniować sposób przekształcania opisu w danym języku (instancji modelu) na inne elementy. Mogą nimi być pliki XML, kod w języku C#, strony HTML itd. Generalna idea jest następująca: prosty opis w języku dziedzinowym przekształcamy automatycznie na elementy o wiele bardziej skomplikowane. Krótko mówiąc oszczędzamy sobie roboty.

W niniejszym artykule przedstawię Wam narzędzia, pozwalające na osiągnięcie tego, o czym pisałem w poprzednim akapicie. We wrześniu 2006 roku Microsoft wydał wersję 3.0 SDK do Visual Studio (http://msdn2.microsoft.com/en-us/vstudio/aa718368.aspx). W pakiecie tym znalazła się pierwsza wersja zestawu Domain-Specific Language Tools. Przy pomocy tych narzędzi możemy projektować graficzne języki dziedzinowe oraz szablony tekstowe wykorzystywane do generowania elementów.

Przykładowy problem

W ASP.Net 2.0 mamy pliki map stron (Web.sitemap). Są to pliki XMLowe służące do opisu zawartości serwisu. Z takiego pliku korzystają kontrolki wspomagające nawigację. Aby pokazać Wam, co można osiągnąć przy pomocy DSL Tools, zbuduję graficzny język do opisu mapy strony. Pokażę także, jak z takiego opisu wygenerować poprawny plik mapy strony, oraz jak umieścić wygenerowany język w VS tak, aby generatora można było używać w różnych projektach.

Najpierw należy określić dziedzinę problemu. Spójrzmy na zawartość przykładowego pliku Web.sitemap. Głównym elementem tego pliku jest węzeł <siteMap>. Jego dziećmi mogą być węzły <siteMapNode> z tym, że element <siteMap> może mieć co najwyżej jeden węzeł podrzędny. Przykładowy plik wygląda następująco:

<?xml version="1.0" encoding="utf-8" ?>
<
siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
 
 
<siteMapNode title="Home" url="Default.aspx">
   
    <
siteMapNode title="SubPage" url="SubPage.aspx" />
 
 
</siteMapNode>
</
siteMap>

Kod 1. Przykładowy opis mapy strony

Z punktu widzenia dziedziny można wyróżnić trzy pojęcia. Pierwszym jest węzeł główny – jest to węzeł <siteMapNode> będący bezpośrednim potomkiem węzła <siteMap>, oraz węzły potomne – węzły <siteMapNode> zagnieżdżone w węzłach <siteMapNode>. Poza tym mamy pojęcie relacji ‘jest podstroną’, która łączy węzły podrzędne z węzłami nadrzędnymi. Zatem w powyższym przykładzie węzeł o tytule „SubPage” jest podstroną węzła o tytule „Home”. Te trzy pojęcia wystarczą nam, aby określić język opisu mapy strony.

Domain-Specific Language Tools

Na Domain-Specific Language Tools składają się następujące komponenty:

  • Kreator, który tworzy projekty szkieletowe języków dziedzinowych. Te projekty następnie można dostosować do własnych potrzeb.
  • Graficzne narzędzie służące do definiowania modelu dziedziny.
  • Zbiór generatorów kodu. Owe generatory będą generować opisany język dziedzinowy w postaci elementów integrujących się z VS.
  • Ramy do definiowania szablonów tekstowych. Dzięki niej możemy tworzyć szablony tekstowe, które z modelu (opisu w danym języku dziedzinowym) mogą generować różne elementy np. pliki XML, kody źródłowe programów itp.
Rysunek 1 obrazuje zastosowanie każdego z wymienionych elementów. Proces tworzenia graficznego języka dziedzinowego przebiega następująco. Zaczynamy od uruchomienia kreatora, który zbierze podstawowe informacje na temat tworzonego języka. Następnie za pomocą graficznego edytora (notabene będącego innym graficznym językiem dziedzinowym) tworzymy opis modelu dziedziny. Kompilujemy i uruchamiamy projekt. Uruchamia się kolejna eksperymentalna instancja Visual Studio, z którą została zintegrowana definicja tworzonego języka. Tu możemy testować ten język jak i definiować szablony tekstowe, które będą używane do generowania elementów. Jeżeli chcemy coś zmienić (ulepszyć) zamykamy eksperymentalną instancję i wracamy do edytora graficznego. Taki cykl powtarzamy aż do uzyskania satysfakcjonujących nas wyników. Na koniec, gdy język jest już gotowy tworzymy instalator i możemy go rozpowszechniać.

 

Rysunek 1. Proces tworzenia i testowania języka dziedzinowego

Rysunek 2 przedstawia okna, które służą do pracy z modelem. Należy tutaj wskazać dwa nowe okna, których używa się do określania własności tworzonego języka. Są to okna: DSL Explorer oraz DSL Details. W pierwszym z nich mamy dostęp do własności globalnych tworzonego języka. Drugie pozwala na definiowanie własności konkretnych elementów.



Rysunek 2. VS z wyróżnionymi oknami narzędziowymi używanymi podczas pracy z modelem

Na rysunku 3 pokazana jest powierzchnia diagramu, na którym definiujemy dziedzinę języka. Po lewej stronie definiujemy elementy modelu (pojęcia) oraz zależności między nimi, a po prawej (za pionową linią) elementy graficzne, które będą reprezentowały pojęcia z dziedziny.


Rysunek 3. Powiększony obszar roboty, na którym definiujemy model języka

Ostatnią, równie ważną rzeczą, jest struktura wygenerowanych projektów. Wspominałem już o kreatorze, który na początku zbiera podstawowe informacje o tworzonym języku. Otóż po zakończeniu tego kreatora zostaną wygenerowane dwa projekty: DSL oraz DSLPackage. Są to projekty, których większą część stanowią pliki szablonów tekstowych, z których będą generowane klasy implementujące funkcjonalności tworzonego języka. Pierwszy to implementacja tworzonego języka a drugi implementacja komponentów integrujących się z VS. Najważniejszym plikiem jest plik DslDefinition.dsl znajdujący się w pierwszym z wymienionych projektów. Jest to plik centralny i de facto jedyny plik modyfikowany podczas tworzenia języka. To w nim znajduje się definicja modelu dziedziny oraz elementów graficznych reprezentujących jego elementy.

Tworzenie projektu języka dziedzinowego

Po zainstalowaniu SDK do Visual Studio w szablonach projektów w kategorii Extensibility będą dostępne dwa nowe projekty: Domain-Specific Language Designer oraz Domain-Specific Language Setup. Pierwszy z nich tworzy projekt graficznego języka dziedzinowego a drugi instalator gotowego języka. Po wybraniu tego pierwszego uruchomi się kreator nowego języka. Najważniejszym krokiem jest krok pierwszy, w którym musimy wybrać szablon jezyka. Do wyboru mamy cztery typy: Class Diagrams, Task Flow, Component Models oraz Minimal Language. Na podstawie wybranego typu zostanie wygenerowany projekt DSL, który będzie potem wyjściowym dla definiowanego języka. Dzięki temu ilość zmian, jakie trzeba będzie wprowadzić, została zminimalizowana. Należy zastanowić się, który typ z przedstawionych wyżej jest najbliższy językowi, który chcemy tworzyć. Każdy z tych typów ma swoje cechy charakterystyczne. Class Diagrams to język podobny do diagramów klas znanych z UML. Zawiera takie pojęcia jak obiekty, relacje między nimi oraz ich własności. Task Flow podobny jest do diagramów aktywności znanych z UML, a Component Models – diagramów komponentów UML. Ostatni, czwarty typ jest najbardziej ogólny i jest punktem startowym dla projektów, których nie można sklasyfikować do żadnej z wcześniej wymienionych grup.

Na potrzeby niniejszego artykuły utworzymy projekt bazując na szablonie Minima Language. Nie wiem dlaczego, ale nie znalazłem opcji utworzenia pustego modelu. Po zakończeniu kreatora w VS automatycznie zostanie wczytany plik DslDefinition.dsl. Jak już wspomniałem wcześniej to jedyny plik, który podlega edycji. Na poniższym rysunku przedstawiam model dziedziny zbudowany od zera po wcześniejszym usunięciu modelu wygenerowanego przez kreatora. W dalszej części artykułu będę się odwoływał do tego rysunku.


Rysunek 4. Model języka dziedzinowego z zaznaczoną kolejnością wykonywanych podczas jego tworzenia kroków

Definiowanie modelu dziedziny zaczynamy od określenia jej elementów. Analizując problem wyodrębniliśmy ich kilka. Między innymi jest tam węzeł główny i węzły potomne. Na pusty diagram na lewą część przeciągnąłem z *** narzędzi dwa komponenty Domain Class. Pierwszy z nich nazwałem SiteMap (1) i będzie on odpowiadał węzłowi głównemu. Drugi z nich nazwałem SiteMapNode (2) i będzie on reprezentował węzły potomne. Dla każdego z tych komponentów możemy teraz zmieniać ustawienia w oknie własności. Następnym krokiem jest zdefiniowanie zależności między tymi elementami. Mamy do dyspozycji dwa rodzaje zależności: Embedding Relationship (relacja zawierania) oraz Reference Relationship (relacja referencyjna). Największą różnicą między tymi dwoma typami relacji jest to, że wraz z usunięciem rodzica w relacji zawierania usuwane są jego dzieci. Nie dzieje się tak w przypadku relacji referencyjnej. Relacja zawierania jest oznaczona na diagramie linią ciągłą, a referencyjna – linią przerywaną. DSL Tools wymagają, aby każdy element dziedziny był celem dokładnie jednej zależności typu embedding. Z tego powodu utworzyłem relację SiteMapHasNodes (3), która jest typu embedding. Wymaganie to nie dotyczy korzenia drzewa hierarchii (w naszym przypadku jest to element SiteMap). Pozostało nam już tylko określić relację ‘jest podstroną’, która również jest elementem tworzonej dziedziny. Wykorzystamy do tego drugi typ zależności. Chcemy zdefiniować relację (4) mówiącą, że dowolny element SiteMapNode może mieć dowolnie dużo podelementów również typu SiteMapNode. Należy tutaj zwrócić uwagę na prawą stronę relacji. Dany węzeł SiteMapNode może mieć co najwyżej jednego rodzica. Tym sposobem określiliśmy hierarchię elementów dziedziny. W następnym kroku określimy, jakie atrybuty będą miały te elementy. W tym konkretnym przypadku jest tak, że akurat zarówno SiteMap jak i SiteMapNode mają takie same atrybuty: tytuł strony, adres strony i jej opis. Aby zaoszczędzić sobie pracy zdefiniowałem bazowy element (5), który opatrzyłem pożądanymi atrybutami. Następnie wykorzystałem relację dziedziczenia (6) i ustaliłem, że elementy SiteMap oraz SiteMapNode mają dziedziczyć po tym elemencie bazowym. Tym sposobem wyposażyłem oba elementy w te same atrybuty. Następnym krokiem jest zdefiniowanie elementów wizualnych, które będą odpowiadały elementom dziedziny. Najpierw musimy dodać diagram. W DSL Explorerze wywołujemy menu kontekstowe dla węzła głównego i wybieramy „Add New Diagram”. W prawej części obszaru roboczego zatytułowanego „Diagram Elements” zostanie dodany element reprezentujący diagram (7). Najważniejszą czynnością w tym momencie jest określenie, które pojęcie z dziedziny będzie reprezentowane przez diagram. Będzie to element główny, czyli klasa SiteMap. Tą informację musimy umieścić we właściwościach diagramu (8). W następnej kolejności możemy dodawać inne elementy graficzne znajdujące się na pasku narzędzi. Ja wykorzystałem element Geometry Shape (9), który będzie reprezentował klasę SiteMapNode. We właściwościach tego elementu możemy ustalić jak ma wyglądać. Możemy wybrać kolor wypełnienia, kształt oraz grubość linii brzegowej. Do wizualizacji relacji wykorzystałem element Connector (10). W tym przypadku mogłem określić kolor i grubość linii oraz typ zakończenia. Teraz należy połączyć elementy wizualne z elementami dziedziny. Służy do tego narzędzie Diagram Element Map znajdujące się na pasku narzędzi. Za jego pomocą przypisałem element graficzny SiteMapNodeShape do elementu SiteMapNode oraz element graficzny ChildParent do relacji SiteMapNodeReferencesChildren. Jest jeszcze jedna rzecz, którą chciałem uzyskać. W tworzonym przeze mnie języku zdecydowałem się na reprezentację elementów mapy strony poprzez prostokąty z zielonym wypełnieniem. Chciałem, aby wewnątrz tych prostokątów pojawiały się tytuły stron przez nie reprezentowane jak i ich adresy URL. Do tego celu służą elementy zwane dekoratorami. Za ich pomocą możemy umieszczać tekst oraz ikony na definiowanych elementach graficznych. Ja wykorzystałem dwa dekoratory tekstowe, z których jeden prezentuje tytuł strony a drugi jej adres URL. Najważniejszym zadaniem podczas definiowania dekoratora jest połączenie go z odpowiednim atrybutem reprezentowanego elementu. Możemy to zrobić korzystając z okna DSL Details. Pozostała nam już do wykonania ostatnia czynność. Należy jeszcze określić elementy *** narzędzi VS, z których po integracji będzie można używać podczas pracy z językiem. Wykorzystałem do tego okno DSL Explorer (12).

Oczywiście wszystkich tych powyższych operacji nie musimy robić na raz. Po każdej małej zmianie możemy uruchomić i przetestować zdefiniowany język. W trakcie takich testowych uruchomień mamy możliwość definiowania szablonów tekstowych, które na podstawie pliku modelu będą generowały pożądane przez nas elementy. W naszym przypadku ma to być plik XML o zadanej strukturze. Na poniższym rysunku prezentuję cały szablon tekstowy, który na podstawie zdefiniowanego wcześniej modelu dziedziny generuje plik mapy strony. Nie będę się zagłębiał w szczegóły tworzenia takich szablonów. Skupię się bardziej na funkcjonalności. Przede wszystkim szablon taki musi być powiązany z plikiem modelu. W tym przypadku mamy to określone w linii 7 – jest to plik SiteMap.sm. W linii 8 wywoływana jest metoda generująca plik mapy strony. Przyjrzyjmy się teraz dokładnie jak funkcja Generate buduje ten plik. W zdefiniowanym modelu dziedziny mieliśmy pojęcia węzła głównego, który był reprezentowany przez obiekt SiteMap. W poniższym przykładzie jest on generowany do postaci węzła siteMapNode (linie 18-21), który jest nadrzędny w stosunku do pozostałych węzłów siteMapNode. Pozostałe elementy modelu generowane są w pętli foreach. Pętla ta generuje drzewa elementów zależnych począwszy od elementów, które nie mają rodzica, czyli elementów będącymi bezpośrednimi potomkami węzła głównego. Wykorzystuję do tego funkcję rekurencyjną, budującą napis reprezentujący to drzewo. Gotowy szablon należy zapisać w postaci szablonu VS. Służy do tego opcja menu File->Export template.

01 <#@
    template
    inherits
=
    "Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation"

    debug="true"#>
02 <#@ assembly name = "System.Xml.dll" #>
03 <#@ output extension=".sitemap" #>
04 <#@ import namespace="System.Text" #>
05 <#@ import namespace="System.Xml" #>
06 <#@ import namespace="System.IO" #>
07 <#@ SiteMapLanguage
   
processor
="SiteMapLanguageDirectiveProcessor"
    requires="fileName='SiteMap.sm'" #>
08 <#=Generate(this.SiteMap)#>
09 <#+
10    private string Generate(SiteMap siteMap)
11    {
12        StringWriter sw = new StringWriter();
13        XmlWriterSettings settings = new XmlWriterSettings();
14        settings.Indent = true;
15        using (XmlWriter xtw = XmlWriter.Create(sw, settings))
16        {
17            xtw.WriteStartElement("siteMap",
                  "http://schemas.microsoft.com/AspNet/SiteMap-File-1.0");
          
18            xtw.WriteStartElement("siteMapNode");
19            xtw.WriteAttributeString("url", siteMap.Url);
20            xtw.WriteAttributeString("title", siteMap.Title);
21            xtw.WriteAttributeString("description", siteMap.Description);
          
22            foreach(SiteMapNode node in siteMap.Nodes)
23            {
24                if(node.Parent == null)
25                    GenerateNodeAux(xtw, node);
26            }
                
27            xtw.WriteEndElement(); 
28            xtw.WriteEndElement();
29        } 
30        return sw.ToString();
31    }

32    private void GenerateNodeAux(XmlWriter xtw, SiteMapNode node)
33    {
34        xtw.WriteStartElement("siteMapNode");
35        xtw.WriteAttributeString("url", node.Url);
36        xtw.WriteAttributeString("title", node.Title);
37        xtw.WriteAttributeString("description", node.Description); 
38        foreach (SiteMapNode childNode in node.Children)
39        {
40            GenerateNodeAux(xtw, childNode);
41        } 
42        xtw.WriteEndElement();
43    }
44 #>

Kod 2. Szablon tekstowy wykorzystujący zdefiniowany model do generowania pliku opisy mapy strony.

Na sam koniec dodamy jeszcze jeden projekt do naszego rozwiązania. Będzie to drugi z wymienionych na początku projektów, czyli Domain-Specific Language Setup. Tym sposobem przygotujemy zdefiniowany język do rozpowszechniania. Aby uniknąć problemów przed dodaniem projektu instalacyjnego należy przebudować cały projekt w wersji release. Następnie należy dodać projekt instalatora. Ostatnią czynnością będzie zintegrowanie szablonu tekstowego z instalatorem. Wykonuje się to ręcznie edytując plik zawierający opis instalatora (InstallerDefinition.dslsetup). W węźle <vsItemTemplates> dodałem następujący wpis (wcześniej umieściłem plik z szablonem w podkatalogu Files projektu Setup):

<vsItemTemplate localeId="1033" targetDirectories="CSharp" templatePath="Files\SiteMapTemplate.zip"/>

Kod 3. Kod rozszerzający definicję instalatora, dodający do Visual Studio nowy typ elementu - plik opisu mapy strony.

Dzięki niemu instalator wśród szablonów elementów umieści również plik szablonu tekstowego. Zapisujemy plik i wykonujemy polecenie Transform all templates, aby wygenerować elementy projektu instalatora. Następnie kompilujemy ten projekt. Po tej operacji mamy już gotowy język do rozpowszechniania. Na poniższym rysunku prezentuję przykładową mapę strony opisaną w utworzonym języku:


Ryzunek 5. Przykładowy opis mapy strony wykonany przy pomocy zaprojektowanego języka dzedzinowego

Podsumowanie

W niniejszym artykule przybliżyłem Wam możliwości jakie dają DSL Tools. Przedstawiłem prosty problem i opisałem jego dziedzinę. Następnie utworzyłem, za pomocą prezentowanych narzędzi, model języka dziedzinowego przeznaczonego do modelowania zagadnień z rozpatrywanej dziedziny. Z miejsca chciałbym Was wszystkich zachęcić do zapoznania się z przedstawionymi tu narzędziami. Na pewno w Waszych głowach już pojawiły się pomysły, jak można takie graficzne języki wykorzystać. Należy tutaj podkreślić, że generowanie części projektu niesie ze sobą wiele korzyści. Obecnie mam przyjemność pracować przy projekcie, w którym co raz więcej elementów jest generowanych. Począwszy od warstwy dostępu do bazy danych po co raz to wyższe warstwy. Dzięki temu praca staje się prostsza i pozwala skupić się na zagadnieniach biznesowych a nie na mozolnym tworzeniu za każdym razem tych samych elementów. Poza tym w pewnym stopniu eliminuje się czynnik ludzki, co pozwala ograniczyć ilość powstających błędów. Wszystkie te zalety mają jeden wspólny mianownik. Otóż dzięki generatorom oszczędzamy czas. A jak wiadomo czas to pieniądz.

Opublikowane 23 stycznia 2007 18:12 przez nuwanda

Komentarze:

Brak komentarzy

Komentarze anonimowe wyłączone