2. DSL - Plan
Co to jest DSL i do czego może się przydać
Rodzaje DSL - dlaczego wewnętrzne, statycznie typowane?
Klocki do budowy DSL
Co dalej? Gdzie się zatrzymać?
3. Maciek Próchniak
Niedoszły topolog algebraiczny
Kontrybutor Activiti
Integracja: Servicemix, Camel, Drools, OSGi, BPEL...
Rzeczy mniej przyjemne: GWT, Smartclient, ...
Last but not least - TouK ;)
4. DSL
Domain
Chcemy móc opisać obiekty, reguły z naszej
dziedziny
Specific
Nie zaprzątając sobie głowy resztą świata
Language
W jakimś języku (programowania)
5. DSL - czy nie mamy już wystarczająco dużo języków?
7. DSL - po co?
Opisywanie reguł biznesowych
Łatwiejesze przełożenie wymagań na kod
Współpraca z ekspertami
Zwiększona produktywność
8. Kto korzysta z DSL?
Programiści/Deweloperzy?
PO/Eksperci biznesowi?
9. Cobol fallacy
”Now we can get rid of programmers and have business people
specify the rules themselves”
- Cobol Fallacy wg. Martina Fowlera
10. Znajomość czynna i znajomość bierna
Znajomość bierna:
”Java je jedn´ z nejpouˇ´ ejˇ´ programovac´ jazyk˚ na
ım zıvanˇ sıch ıch u
svˇtˇ. Podle Tiobe indexu je Java nejpopul´rnˇjˇ´ programovac´
ee a e sı ı
jazyk. (...) Dne 8. kvˇtna 2007 Sun uvolnil zdrojov´ kódy Javy (cca
e e
2,5 milión˚ ˇ´dk˚ kódu) a Java bude d´le vyv´
u ra u a ıjena jako open
source.”
- http://cs.wikipedia.org/wiki/Java (programovac´ jazyk)
ı
Znajomość czynna?
To już trudniejsze...
11. DSL - po co?
Opisywanie reguł biznesowych
Łatwiejesze przełożenie wymagań na kod
Współpraca z ekspertami
Zwiększona produktywność
12. Ale przecież mamy DDD?
Bogaty model domenowy to
podstawa
DSL - ”widok” na model
domenowy
Model domeny to
trudniejszy fragment
13. Inny zestaw reguł
Przy budowaniu DSL zwykle łamiemy ”żelazne” zasady:
metody globalne/statyczne?
CQS?
prawo Demeter?
”małe” klasy?
15. DSL - ale jaki?
zewnętrzny (external)
SQL
Regexp
css
schematy XML?
wewnętrzny (embedded, DSEL) - jest osadzony w GPL
Apache Camel
Mockito
JMock
Rails
16. Dlaczego wewnętrzne?
Dostajemy ”za darmo”:
Parser
Kompilator
Wsparcie IDE
Rozszerzanie i obsługa niestandardowych przypadków
17. Przykład: WS HumanTask
Schemat XML opisu zadań do wykonania przez człowieka
W typowym projekcie zadania są podobne
Problem: jak zdefiniować typy właściwe dla konkretnego
projektu
18. Przykład: WS BPEL
Schemat XML opisu działania procesu biznesowego
Konieczność ujęcia wielu rodzajów akcji
Problem: język staje się GPL
19. Przykład pośredni: Drools
Własny język definicji reguł
Konieczność ujęcia wielu rodzajów akcji
Korzyści: połączenie wyrażalności zewnętrznego DSL i
możliwości Javy
1 r u l e AcceptOrder
2 when
3 $ c l i e n t : C l i e n t ( s t a t u s==APPROVED)
4 $ o r d e r : O r d e r ( s t a t u s== NEW, c l i e n t==$ c l i e n t )
5 then
6 orderService . acceptOrder ( $order )
7 end
20. Dlaczego statycznie typowane?
Języki dynamiczne oferują duże
możliwości, ale:
Wsparcie IDE
Możliwości odkrywania API
Każdy projekt trafi w końcu
do studentów...
21. Przykład - Apache Camel
Implementacja wzorców EIP: router, splitter, aggregator, lista
odbiorców, ...
Definiowanie przepływów komunikacji
Przykład:
1 c l a s s JamRouteBuilder extends R o u t e B u i l d e r {
2 public void c o n f i g u r e () {
3 from ( ” f i l e : / tmp/ i n ” )
4 . convertBodyTo ( S t r i n g . c l a s s )
5 . bean ( ” r e c i p i e n t R e p o s i t o r y ” )
6 . r e c i p i e n t L i s t ( property (” recipients ” ));
7 }
8 }
22. Płynny interfejs - klucz do sukcesu DSL
Płynny interfejs
Klasyczny model obiektowy
23. Płynny interfejs - rozszerzony budowniczy
1 makeOrder (NORMAL)
2 . w i t h P r i c e ( new B i g D e c i m a l ( 2 2 . 0 0 ) )
3 . w i t h D a t e ( ”2011−11−12” )
4 . finish ();
1 p u b l i c O r d e r B u i l d e r makeOrder ( OrderType t y p e ) {
2 r e t u r n new O r d e r B u i l d e r ( t y p e ) ;
3 }
4 //w k l a s i e O r d e r B u i l d e r
5 public OrderBuilder w i t h P r i c e ( BigDecimal p r i c e ) {
6 this . price = price ;
7 return this ;
8 }
24. Płynny interfejs - problem stopu
1 makeOrder (NORMAL)
2 . w i t h P r i c e ( new B i g D e c i m a l ( 2 2 . 0 0 ) )
3 . w i t h D a t e ( ”2011−11−12” )
4 . finish ();
1 p u b l i c Order f i n i s h ( ) {
2 return this . build ()
3 }
25. Płynny interfejs - problem stopu
1 makeOrder (NORMAL)
2 . w i t h P r i c e ( new B i g D e c i m a l ( 2 2 . 0 0 ) )
3 . w i t h D a t e ( ”2011−11−12” )
4 . withClientId (1334);
Id klienta jest wymagane:
1 p u b l i c Order w i t h C l i e n t I d ( long l ) {
2 clientId = l ;
3 return this . build ( ) ;
4 }
26. Płynny interfejs - wymuszanie kolejności
1
2 interface ClientPhoneBuilder {
3 S t r e e t B u i l d e r w i t h P h o n e ( S t r i n g phone ) ;
4 S t r e e t B u i l d e r withoutPhone ( ) ;
5 }
6
7 interface StreetBuilder {
8 ClientApartmentBuilder livingOn ( String street );
9 C l i e n t homeless ( ) ;
10 }
11 ...
1
2 . w i t h P h o n e ( ” 2245556 ” ) . l i v i n g O n ( ” J e r o z o l i m s k i e ” ) .
27. Płynny interfejs - zagnieżdżanie
Możliwości:
hierarchiczne buildery
dodatkowe buildery
28. Zagnieżdżanie builderów - hierarchie
1 . f i l t e r ()
2 . x p a t h ( ” // o r d e r I d ” )
3 . isEqualTo (15)
4 . send ( ” http : / / . . . ” )
1 class RouteBuilder {
2 ...
3 ExpressionBuilder f i l t e r () {
4 t h i s . f i l t e r = new E x p r e s s i o n B u i l d e r ( t h i s ) ;
5 return this . f i l t e r ;
6 }
7 v o i d s e n d ( S t r i n g where ) {
8 ...
9 }
29. Zagnieżdżanie builderów - hierarchie
1 . f i l t e r ()
2 . x p a t h ( ” // o r d e r I d ” )
3 . isEqualTo (15)
4 . send ( ” http : / / . . . ” )
1 c l a s s E x p r e s s i o n B u i l d e r <T> {
2
3 T parent ;
4
5 E x p r e s s i o n B u i l d e r xpath ( S t r i n g xpath ) {
6 t h i s . e x p r e s s i o n = new X P a t h E x p r e s s i o n ( x p a t h ) ;
7 return this ;
8 }
9 T isEqualTo ( String value ) {
10 t h i s . a s s e r t i o n = new E q u a l i t y ( v a l u e ) :
11 return parent ;
12 }
13 }
30. Zagnieżdżanie builderów - oddzielne buildery
1
2 c la s s SenderRouter extends RouteBuilder {
3 ...
4 . filter (
5 x p a t h ( ” // o r d e r I d ” ) . i s E q u a l T o ( 1 5 )
6 ) . send ( ” http : / / . . . ” )
7 ...
8 E x p r e s s i o n B u i l d e r xpath ( S t r i n g xpath ) {
9 r e t u r n new X P a t h B u i l d e r ( x p a t h ) ;
10 }
11 }
Obiekt kontekstu jest lepszy niż funkcje globalne
1 class XpathBuilder extends ExpressionBuilder {
2 Expression isEqualTo ( String value ) {
3 t h i s . a s s e r t i o n = new E q u a l i t y ( v a l u e ) :
4 return build ( ) ;
5 }
31. Płynny interfejs - zagnieżdżanie
hierarchiczne buildery
lepszy dostęp do kontekstu
lepsze podpowiedzi IDE
dodatkowe buildery
prostsze
reużywalność
rozszerzalność
32. Co dalej?
Cały czas dużo szumu syntaktycznego:
1 from ( ” s e d a : a ” )
2 . choice ()
3 . when ( h e a d e r ( ” f o o ” ) . i s E q u a l T o ( ” b a r ” ) )
4 . to (” seda : b ”)
5 . otherwise ()
6 . p r o c e s s ( new P r o c e s s o r ( ) {
7 p u b l i c v o i d p r o c e s s ( Exchange e x ) {
8 e x . s e t P r o p e r t y ( ” d a t e ” , new Date ( ) ) ;
9 }
10 });
Tu kończą się możliwości Javy...
33. Scala comes to the rescue!
”normalny” język:
programowanie obiektowe
statyczne typowanie
można programować funkcyjnie
prosty pattern matching
dużo lukru syntaktycznego
34. Scala comes to the rescue!
uproszczenia składni, przeciążanie operatorów
domyślne (implicit) konwersje i parametry
funkcje i domknięcia
35. Uproszczenia składni
W wielu miejscach kropki i przecinki stają się opcjonalne:
1 order ()
2 . s i z e ( 1 1 ) . express ( true )
A więc:
1 order ()
2 s i z e 11
3 express true
36. Przeciążanie operatorów
Wszystko jest metodą
1 c l a s s Balance ( balance : BigDecimal ) {
2 d e f + ( toAdd : B a l a n c e ) : B a l a n c e = {
3 new B a l a n c e ( b a l a n c e+toAdd . g e t B a l a n c e ) }
4 def getBalance = balance
5 }
A więc:
1 println
2 ( ( new B a l a n c e ( 1 0 ) + new B a l a n c e ( 1 0 ) ) . g e t B a l a n c e ) ;
37. Domyślne konwersje
Dodawanie metod do klas - monkey patching
Języki dynamiczne robią to globalnie
Jak można to zrobić bezpieczniej?
38. Domyślne konwersje
Dodajemy możliwość autokonwersji w obrębie danej klasy:
1 c l a s s Orders {
2 i m p l i c i t def stringToOrder ( s t r : String ) =
3 new O r d e r B u i l d e r ( s t r ) ;
4 }
5
6 class OrderBuilder ( c l i e n t : String ) {
7 var p r i c e : BigDecimal = 0;
8 def w i t h P r i c e ( newPrice : BigDecimal ) =
9 { p r i c e = newPrice ; b u i l d ; }
10 def build = . . .
11 };
I możemy napisać:
1 c l a s s VIPOrders extends Orders {
2 ” c l i e n t ” withPrice 1.22
3 }
39. Przekazywanie strategii - funkcje i domknięcia
Przekazywanie wyrażeń, predykatów - Java
1 . f i l t e r ( new E x p r e s s i o n ( ) {
2 p u b l i c b o o l e a n e v a l u a t e ( Message message ) {
3 r e t u r n VIP == message . g e t P r o p e r t y ( ” c l i e n t T y p e ” ) ;
4 }
5 }).
Scala:
1 . f i l t e r { e x c h a n g e =>
2 VIP==e x c h a n g e . g e t P r o p e r t y ( ” c l i e n t T y p e ” ) }
40. Przekazywanie strategii - funkcje i domknięcia
Przekazywanie wyrażeń, predykatów - Scala
1 ” d i r e c t : b” == { >
2 when ( . i n == ”< h a l l o />” ) {
3 −−> ( ”mock : b” )
4 t o ( ”mock : c ” )
5 } otherwise {
6 p r o c e s s ( e x => e x . s e t P r o p e r t y ( ” d a t e ” , new Date ( ) ) )
7 }
8 t o ( ”mock : d” )
9 }
41. 1 case c l a s s SChoiceDefinition
2 ( override val target : ChoiceDefinition ) . . . {
3 override def otherwise = {
4 target . otherwise
5 this
6 }
7
8 o v e r r i d e d e f when ( f i l t e r : Exchange => Any ) = {
9 val predicate = PredicateBuilder . toPredicate ( f i l t e r )
10 t a r g e t . when ( p r e d i c a t e )
11 this
12 }
13 }
42. Granice czytelności i wyrażalności
Język naturalny:
1 Stworz zamowienie
2 d l a k l i e n t a ”TouK”
Fleksja?
Synonimy?
Koniugacja?
43. O jeden krok za daleko?
Definiowanie
1 class OrderBuilder ( c l i e n t : String ) {
2 var nameField : S t r i n g = ””;
3 def name = { n a m e F i e l d = }
4 def with a = t h i s ;
5 };
By napisać
1 c l a s s VIPOrders extends Orders {
2 ” c l i e n t ” . w i t h a name ” a l a ” ;
3 }
45. Rozszerzanie - przykład
DSL do bardzo prostych procesów stanowych
interakcje oparte na Apache Camel
1 s t a r t ( ” d i r e c t : employee ” ) . f o r k (
2 s t a t e ( ” h r S t a r t ” ) . to ( ” f i l e :/// hr ” )
3 . w a i t S t a t e ( ” waitingForHR ” , ” f i l e :/// h r I n ” )
4 . s t a t e ( ” hrEnd ” ) ,
5 s t a t e ( ” i t S t a r t ” ) . to ( ” f i l e :/// i t ” )
6 . waitState (” waitingForIT ” ,” f i l e :/// i t I n ”)
7 . state (” itEnd ”)
8 ) . s t a t e ( ” end ” ) ;
46. Rozszerzanie - przykład
Trudno ”wpiąć” się ze swoimi rozszerzeniami
Problem: Użycie zagnieżdżonych builderów
Możliwe rozwiązania:
Używanie oddzielnych builderów i obiektu kontekstu
Nadpisanie pewnej części źródłowego DSL - może być
skomplikowane
47. Podsumowując...
DSL może zwiększyć czytelność i zwięzłość kodu
Wewnętrzne DSLe w statycznie typowanych językach mają
niski koszt budowy i nauki
Korzystając ze Scali możemy zmniejszyć szum syntaktyczny w
naszym DSLu
Projektując DSL trzeba myśleć o jego rozwoju i możliwościach
rozszerzania
49. I inne zasoby
http://weblogs.java.net/blog/carcassi/archive
/2010/02/04/building-fluent-api-internal-dsl-java
http://pragdave.blogs.pragprog.com/pragdave/2008/03/the-
language-in.html
http://www.infoq.com/articles/internal-dsls-java