Showing posts with label dsl. Show all posts
Showing posts with label dsl. Show all posts

Thursday, May 29, 2014

DDD from a Non-Java/C# Developer Perspective

During 4Developers I met some friends who are PHP and Perl developers. They were very interested in what kind of value Domain Driven Design may bring to them.

They found the topic very confusing because in their opinion speakers were strongly focused on Java and Object-Oriented programming. Those developers made connection between DDD and OO-programing especially in Java and C#. I think it's kind of misunderstanding.

Let's see some examples of what domain expert could say (credits to Sławek):

Domain expert says:

When a piece of art is viewed as a thumbnail by a customer, one is able to show interests in this piece of by adding it to the LightBox.

The system should generate the offer from the reservation.

An offer contains just only available products with current prices.

A customer is not obliged to accept the offer. One is able to accept or reject it as well.

and so forth...

So we have some domain scenarios in a very unstructured form as were expressed by the expert.

To build the right software we model a domain base on expert knowledge and domain scenarios.

And here I wanna to put strict distinction: the goal is to model the domain but using building blocks given by DDD community is just a way to achieve this goal in the Object-Oriented world.

In my opinion pure DDD modeling should be based on Domain-Specific Languages when we have language to directly express what the expert says and how the domain works. This programing style is even easier to apply with some script and loosely typed languages.

So, if you are non Java/C#/OO professional, please be sure you may get benefits with using DDD. You don't need building blocks in the form they are currently presented, because they are implementation of DDD in Object-Oriented context. The core is to model the domain and maintain pure isolated model in a form suitable to the technology you use.

Tuesday, April 21, 2009

Domain Specific Language - XML i Fluent Interface




Zamiast rozwodzić się nad definicjami i pojęciami zacznę od pewnego problemu, z którym się spotkałem. Jakiś czas temu do generowania raportów używaliśmy biblioteki JasperReports. To naprawdę rewelacyjna biblioteka, której ideę działania przedstawia Rysunek 1:

Przy całej swej wspaniałości korzystanie z tej (i pewnie z innych) biblioteki do generowania raportów ma następujące konsekwencje:

  • Szybko generuje się tylko płaskie raporty czyli takie, w których wynikowy raport można „pociąć” na części generowane z jednego szablonu np. spis treści, lista płac, podsumowanie. Jeśli chcielibyśmy zagnieżdżać raport w raporcie, to oczywiście JasperReports daje taką możliwość, ale o całe przygotowanie danych, odpowiednią konfigurację definicji wyglądu i dostarczanie silnikowi danych w odpowiednich porcjach należy zadbać własnoręcznie.

  • Jeżeli raport musi odwzorowywać wiedzę dziedzinową, np. raportowane są wyniki testu IQ i związana jest z tym złożona merytoryka, to albo bardzo skomplikowane jest przygotowywanie danych a konfiguracja definicji wyglądu w miarę prosta albo odwrotnie. Trudno znaleźć złoty środek.

  • Ubogacanie raportów wykresami jest możliwe, lecz bardzo pracochłonne.

  • Jeśli automatyzowane są raporty, które do tej pory były tworzone ręcznie, to...czeka nas baaaardzo dużo pracy.


Framework raportowy


Powstał pomysł, aby problemom zaradzić. Stworzone rozwiązanie powinno zapewniać następujące funkcjonalności:

  • Definiowanie fragmentów raportu (podraportów), które można ze sobą łączyć sekwencyjnie (jeden za drugim) lub poprzez agregację (jeden w drugim)

  • Raport można zasilać danymi z: zapytania SQL, zapytania MDX, wyrażenia XPath lub Expression Language

  • Raport można wzbogacić wykresem z dostępnego zbioru wykresów.

  • I najważniejsze – wszystko wyżej wymienione powinno odbywać się deklaratywnie.


Została stworzona nadbudówka (warstwa abstrakcji – uwielbiam to wyrażenie!) na JasperReports, która pozwoli deklaratywnie tworzyć raporty. Jak zatem opisywać (deklarować) raporty tak, aby sprostać wymaganiom? Wybór padł na XML. Rysunek 2 prezentuje szkic rozwiązania.

Pojawiły się nowe elementy całego rozwiązania: plik XML, w którym będzie deklarowany raport (a konkretnie podraporty oraz zależności pomiędzy nimi) oraz nadbudówka, która przejmuje kontrolę nad silnikiem JasperReports. Oczywiście pojawia się dodatkowa warstwa pośrednicząca pomiędzy klientem, który chce wygenerować raport, a biblioteką. Wszystko ma swoją cenę. W tym przypadku prostota tworzenia raportów kosztuje dodatkową pracę przy implementacji warstwy pośredniczącej.

Spójrzmy na zamieszczony przykład pliku opisującego raport (na potrzeby artykułu nieco uproszczonego).

<reportConfiguration>
<datasources>
<datasource name="hsql" default="true" type="sql">
<property name="driver" value="org.hsqldb.jdbcDriver" />
<property name="url" value="jdbc:hsqldb:hsql://localhost/mydb" />
<property name="username" value="sa" />
<property name="password" value="" />
</datasource>
</datasources>

<tableOfContents enabled="true" position="begin" designFile="tableOfContents.jrxml" />

<subreports>
<subreport type="static" name="introduction" designFile="introduction.jrxml" />

<subreport type="sql" name="employeesList" designFilePath="employeesList.jrxml"
query="select * name,surname from emplyees" />
</subreports>
</reportConfiguration>


Pojawiły się tu słowa (w postaci znaczników XML) jak reportConfiguration, tableOfContents, subreport, desigFilePath. Nie są to słowa związane z językiem programowania, lecz przeznaczone do opisu jednego konkretnego problemu – deklaratywnego tworzenia raportów. Przy użyciu XML zdefiniowaliśmy język opisu raportów, język opisu specyficznego problemu – Domain Specific Language (DSL).

DSL - implementacja oparta o XML


Z punktu widzenia użytkownika opisującego raport XML jest wygodnym rozwiązaniem. W tym przypadku użytkownikami byli programiści, którzy dodawali funkcjonalność generowania raportów do systemów nad którymi pracowali. Ale czy takie rozwiązanie jest najlepsze z perspektywy całego zespołu, w tym grupy odpowiedzialnej za rozwijanie Framework raportowego? Aby odpowiedzieć na to pytanie należy przyjrzeć się sposobowi zaimplementowania tego rozwiązania.

Pierwszym krokiem jest odczyt konfiguracji z pliku XML. Wygląda to na dość żmudne zadanie. Ale zauważmy, że składnia XML ma charakter obiektowy. Znacznik odpowiada obiektowi, a atrybuty znacznika, atrybutom obiektu. Powstaje pytanie: czy istnieje biblioteka pozwalająca automatycznie przekształcić plik XML do odpowiadającego mu modelu obiektowego? Istnieje – Apache Betwixt (być może są też inne). Rozwiązanie przybiera następującą postać ja na Rysunek 3.

Każdemu znacznikowi z pliku XML odpowiada klasa Java. Biblioteka Betwixt jest odpowiedzialna za przekształcenie struktury XML do analogicznej struktury klas. Aby wykonać to zadanie potrzebna jest dodatkowa konfiguracja. W przypadku tej biblioteki konfigurowanie polega na utworzeniu dla każdej klasy modelu obiektowego pliku .betwixt, w którym zawarte są informacje o mapowaniach znacznik XML – obiekt. Rozwiązanie to przedstawia Rysunek 4.

Zauważmy, że struktura obiektowa odpowiada strukturze pliku XML (Listing 1). DSL w postaci pliku XML ma następujące zalety:

  • Jest intuicyjny i jego utrzymywaniem mogą zajmować się nieprogramiści;

  • Opis raportu jest oddzielony od kodu aplikacji;

  • Można stworzyć elastyczne reguły walidacji składni w oparciu o plik XSD;

  • Oparcie na standardzie XML daje możliwość edytowania pliku w już istniejących narzędziach WYSIWYG lub stworzenia nowego narzędzia do graficznego opisywania raportu;

  • Konwersja do nowej wersji DSL jest łatwa do zaimplementowania, np. za pomocą transformat XSLT lub po stronie języka Java.


Jednocześnie to rozwiązanie ma następujące wady:

  • Występuje dodatkowa warstwa pośrednicząca w postaci biblioteki Betwixt;
  • Framework korzysta już z 3 rodzajów konfiguracji:

    1. Pliki jrxml – wymagane przez bibliotekę JasperReports definiują wygląd graficzny podraportów;

    2. Pliki betwixt – wymagane przez bibliotekę Betwixt mapują znaczniki XML na obiekty Java;

    3. Plik konfiguracyjny tworzonego frameworka – opisuje zależności pomiędzy podrap ortami, jest to punkt centralny naszego rozwiązania;

  • Podczas dodawania nowych funkcjonalności najbardziej pracochłonne są modyfikacje związane z plikiem konfiguracyjnym: mapowania, konwersje danych, obsługa błędów;

  • Ze względu na narzut prac nad plikiem konfiguracyjnym czas oczekiwania na nową wersję biblioteki jest dość długi;

  • Refaktoryzacja kodu na styku kod Java plik XML jest żmudna i trudna do przeprowadzenia;

  • Testowanie konfiguracji XML jest niewygodne – bardzo szybko rozmnażają się testowe pliki konfiguracyjne zawierające różne wersje konfiguracji do przetestowania;

  • Refaktoryzacja testów jest bardzo pracochłonna ze względu na istnienia bardzo wielu plików konfiguracyjnych;



DSL - implementacja oparta o Fluent Interface


W poszukiwaniu alternatywy do implementacji DSL we frameworku raportowym przyjrzyjmy się koncepcji Fluent Interface. Fluent Interface to sposób projektowania API, który łatwo można zobrazować na przykładzie biblioteki JMock.

mock.expects(once()).method("method").withAnyArguments().isVoid();

Trudno o bardziej trafną nazwę niż Fluent Inteface. Wywołania metod można przeczytać jak zdanie w języku naturalnym. Sam koncept nie jest nowy, ja doszukiwałbym się korzeni w kaskadowym korzystaniu ze strumieni w C++ za pomocą operatorów << i >>.

Zaprojektujmy zatem Fluent API do konfiguracji raportu. Powinno ono spełniać następujące założenia:

  • Wywołania kaskady metod czyta się jak zdanie w języku naturalnym;

  • API powinno sugerować właściwą kolejność wywoływania metod.

  • Nowe API nie powinno ingerować w istniejący model obiektowy.


Na początek zajmijmy się klasą DatasourceBean. Potrzebujemy udostępnić metody o odpowiednich nazwach, które będą konfigurowały obiekty tej klasy. Jednocześnie API nie powinno ingerować w już istniejące rozwiązanie. Wymóg separacji API od modelu reprezentującego opis raportu wynika z tego, że nie ma potrzeby, aby obiekty wiedziały w jaki sposób są konfigurowane. W terminologii angielskiej używane jest przy tej okazji słowo agnostic.

Rozwiązaniem spełniającym powyższe kryteria jest wzorzec budowniczego

Odpowiedzialnością klasy DatasourceBeanBuilder jest udostępnienie Fluent API do konfigurowania obiektu klasy DatasourceBean. Praca z API mogłaby wyglądać następująco:


builder.isSqlDatasource()
.withDriver( "org.hsqldb.jdbcDriver" )
.withUrl( "jdbc:hsqldb:hsql://localhost/mydb" )
.withUsername( "sa" )
.withPassword( "" )
.isDefault();

Zapewne domyślasz się, że sztuczka pozwalająca na kaskadowe wywoływanie metod polega na zwracaniu referencji do budowniczego w każdej metodzie. Poniżej implementacja tego budowniczego.

public class DatasourceBuilder {

private DatasourceBean bean = new DatasourceBean();


public DatasourceBuilder withUsername(String username) {
bean.addProperty( new PropertyBean("username", username) );
return this;
}

public DatasourceBuilder withPassword(String password) {
bean.addProperty( new PropertyBean("password", password) );
return this;
}

public DatasourceBuilder withUrl(String url) {
bean.addProperty( new PropertyBean("url", url) );
return this;
}

public DatasourceBuilder withDriver(String driver) {
bean.addProperty( new PropertyBean("driver", driver) );
return this;
}

public DatasourceBuilder isSqlDatasource() {
bean.setType( "sql" );
return this;
}

public DatasourceBuilder isDefault() {
bean.setDefaultDatasource( true );
return this;
}
}


Dla każdej klasy modelu obiektowego będzie istniał budowniczy odpowiedzialny za udostępnienie API do konfiguracji obiektu (Rysunek 6).

Jednym z założeń projektowanego interfejsu było, aby sugerował on programiście kolejność wywoływania metod. Osiągnęliśmy to poprzez wprowadzenie wielu budowniczych. W ten sposób użytkownik, w danym momencie pracuje tylko z jednym budowniczym i ma dostęp do metod, które mają sens w danym kontekście.

Centralnym punktem API jest ReportConfigurationBeanBuilder, który udostępnia użytkownikowi bardziej szczegółowych budowniczych za pomocą swoich metod (Rysunek 7).

Przy obecnym rozwiązaniu użytkownik korzysta z API w następujący:

ReportBuilder report = new ReportBuilder();
report.withMasterDesing( "master.jrxml" )

report.newDataSource( "hsql" )
.isSqlDatasource()
.withDriver( "org.hsqldb.jdbcDriver" )
.withUrl( "jdbc:hsqldb:hsql://localhost/mydb" )
.withUsername( "sa" )
.withPassword( "" )
.isDefault();

report.withTableOfContents()
.designFrom( "tableOfContents.jrxml" )
.atTheBegining();

report.addStaticSubreport( "introduction" )
.designFrom( "introduction.jrxml" )

report.addSqlSubreport( "employeeList" )
.designFrom( "employeeList.jrxml" )
.withQuery( "select * name,surname from emplyees" )
.getDataFrom( "hsql" );

Możemy połączyć konfigurację w jeden ciąg wywołań dodając do każdego budowniczego metodę public ReportBuilder and(). W ten sposób możliwe jest korzystanie z API w następujący sposób:

ReportBuilder report = new ReportBuilder();
report .withMasterDesing( "master.jrxml" )
.newDataSource( "hsql" )
.isSqlDatasource()
.withDriver( "org.hsqldb.jdbcDriver" )
.withUrl( "jdbc:hsqldb:hsql://localhost/mydb" )
.withUsername( "sa" )
.withPassword( "" )
.isDefault()
.and()
.withTableOfContents()
.designFrom( "tableOfContents.jrxml" )
.atTheBegining()
.and()
.addStaticSubreport( "introduction" )
.designFrom( "introduction.jrxml" )
.and()
.addSqlSubreport( "employeeList" )
.designFrom( "employeeList.jrxml" )
.withQuery( "select * name,surname from emplyees" )
.getDataFrom( "hsql" );

Wydaje się jednak, że to rozwiązanie jest mniej czytelne niż poprzednie.

DSL oparty o Fluent Interface ma następujące zalety:

  • Jest bardzo czytelny i wygodny w użyciu;

  • Łatwość tworzenia i utrzymania testów jednostkowych – ponieważ wszystko napisane jest w Javie (innym języku);

  • Wsparcie dla refaktoryzacji kodu i testów jednostkowych;

  • Zdecydowanie szybsza implementacja poprawek i nowych funkcjonalności w porównaniu z rozwiązaniem opartym na XML.


Przy powyższych niewątpliwych zaletach należy mieć świadomość następujących konsekwencji tego rozwiązania:

  • Tylko programista może konfigurować raport;

  • Zaprojektowanie dobrego Fluent API wymaga sporo czasu i wysiłku;

  • Stworzenie narzędzia WYSIWYG do konfigurowania raportu wymaga znacznego nakładu prac.



Krótkie podsumowanie


Zarówno XML jaki Fluent Interface mają swoje plusy dodatnie i ujemne. Można próbować określić zakresy przydatności każdego z rozwiązań. W omawianym przykładzie framework raporty był biblioteką programistyczną. Jedynymi jej użytkownikami byli programiści. Gdybym jeszcze raz brał udział w tym projekcie z pewnością przychylałbym się ku Fluent Interface. Głównym moim argument są korzyści wynikające z łatwości testowania, refaktoryzacji i możliwości szybkiej rozbudowy narzędzia. Gdyby jednak framework miał być wykorzystywany przez zewnętrzne systemy np. narzędzia WYSIWYG to XML, mimo narzutów implementacyjnych, jest rozwiązaniem bezkonkurencyjnym.

Przedstawione tu rozwiązania to nie jedyny sposób tworzenia DSL. Gdy tworzyliśmy framework raportowy pojawiały się pomysły napisania języka do opisu raportów przy użyciu Ruby. Wtedy jednak nie nazywaliśmy tego jeszcze DSL. Zainteresowanych tematem odsyłam do bliki Martina Fowlera.