Groovy reactor grails realtime web devoxx 2013

Preview:

Citation preview

Guillaume Laforge @glaforge !

Groovy, Reactor, Grails & the realtime web

Stéphane Maldini@smaldini !

Stéphane Maldini

Consultant and Reactor committer at .

!

@smaldini

Guillaume Laforge

Groovy project lead at .

!

@glaforge http://glaforge.appspot.com

Les Cast Codeurs

Introduction

Part 0

Valuable usersdon’t like to wait

Realtime webapplications are about

the « now »

Show users liveupdates as they come

Show users liveupdates as they come

A « push » approach vs a« pull » approach

Show users liveupdates as they come

Handle a bigvolume of events

A « push » approach vs a« pull » approach

Show users liveupdates as they come

Handle a bigvolume of events React fast to

new events

A « push » approach vs a« pull » approach

Show users liveupdates as they come

Handle a bigvolume of events React fast to

new events

A « push » approach vs a« pull » approach

Use promises, events,streams to react fast and

stream updates to users asthey are ready

Show users liveupdates as they come

Handle a bigvolume of events React fast to

new events

A « push » approach vs a« pull » approach

Use promises, events,streams to react fast and

stream updates to users asthey are ready

Avoid blockingand state

Show users liveupdates as they come

Handle a bigvolume of events React fast to

new events

Fail gracefully

A « push » approach vs a« pull » approach

Use promises, events,streams to react fast and

stream updates to users asthey are ready

Avoid blockingand state

Sharp tools:Groovy, Reactor &

Grails

Groovy

Part 1

Groovy…

an Open Source alternative language

for the JVM

Groovy…

Object-oriented, dynamic, with a functional flavor

Groovy…

But also supports static type checking &

static compilation

million downloadsper year1.7

Yup, we’re allusing Groovy!

Simplify the life of(Java) developers

Groovy as a Javasuperset

It’s so easy tolearn!

Groovy as a Javasuperset

As safe and fast as Java withstatic type checking & compilation

As safe and fast as Java withstatic type checking & compilation

new  MarkupBuilder().html  {          head  {                  title  "The  Script  Bowl"          }  !

       body  {                  div(class:  "banner")  {                          p  "Groovy  rocks!"                  }          }  }

move  forward  at  3.km/h

Expressive,Concise,Readable

@RestController  class  App  {          @RequestMapping("/")          String  home()  {  "Hello  World!"  }  }

Speaking of conciseness...A full Spring app in the span of a tweet!

Great forscripting

Great forscripting

Fit for Domain-SpecificLanguages

Great forscripting

Fit for Domain-SpecificLanguages

Most seamless integration &interoperability wih java!

Great forscripting

Fit for Domain-SpecificLanguages

Most seamless integration &interoperability wih java!

Full-blown reactiveapplications too!

Most Java code is alsovalid Groovy code!

Any Java developer is aGroovy developer!

Most Java code is alsovalid Groovy code!

Flat learningcurve

Flat learningcurve

Easy to learn

@glaforge — @smaldini / #DV13-rtweb

Scripts versus Classes

!26

public  class  Main  {          public  static  void  main(String[]  args)  {                  System.out.println("Hello");          }  }

vs

@glaforge — @smaldini / #DV13-rtweb

Scripts versus Classes

!26

public  class  Main  {          public  static  void  main(String[]  args)  {                  System.out.println("Hello");          }  }

println  "Hello"

vs

Optional

Optional

Semicolons

Optional

SemicolonsParentheses

Optional

SemicolonsParentheses

return keyword

Optional

SemicolonsParentheses

return keyword public keyword

Optional

SemicolonsParentheses

return keyword public keyword

Typing!

@glaforge — @smaldini / #DV13-rtweb

Optional...

!28

public  class  Greeter  {          private  String  owner;  !        public  String  getOwner()  {                  return  owner;          }  !        public  void  setOwner(String  owner)  {                  this.owner  =  owner;          }  !        public  String  greet(String  name)  {                  return  "Hello  "  +  name  +  ",  I  am  "  +  owner;          }  }  !Greeter  greeter  =  new  Greeter();  greeter.setOwner("Guillaume");  !System.out.println(greeter.greet("Marion"));

@glaforge — @smaldini / #DV13-rtweb

Optional...

!28

public  class  Greeter  {          private  String  owner;  !        public  String  getOwner()  {                  return  owner;          }  !        public  void  setOwner(String  owner)  {                  this.owner  =  owner;          }  !        public  String  greet(String  name)  {                  return  "Hello  "  +  name  +  ",  I  am  "  +  owner;          }  }  !Greeter  greeter  =  new  Greeter();  greeter.setOwner("Guillaume");  !System.out.println(greeter.greet("Marion"));

Semicolons

@glaforge — @smaldini / #DV13-rtweb

Optional...public  class  Greeter  {          private  String  owner  !        public  String  getOwner()  {                  return  owner          }  !        public  void  setOwner(String  owner)  {                  this.owner  =  owner          }  !        public  String  greet(String  name)  {                  return  "Hello  "  +  name  +  ",  I  am  "  +  owner          }  }  !Greeter  greeter  =  new  Greeter()  greeter.setOwner("Guillaume")  !System.out.println(greeter.greet("Marion"))

!29

@glaforge — @smaldini / #DV13-rtweb

Optional...public  class  Greeter  {          private  String  owner  !        public  String  getOwner()  {                  return  owner          }  !        public  void  setOwner(String  owner)  {                  this.owner  =  owner          }  !        public  String  greet(String  name)  {                  return  "Hello  "  +  name  +  ",  I  am  "  +  owner          }  }  !Greeter  greeter  =  new  Greeter()  greeter.setOwner("Guillaume")  !System.out.println(greeter.greet("Marion"))

!30

@glaforge — @smaldini / #DV13-rtweb

Optional...public  class  Greeter  {          private  String  owner  !        public  String  getOwner()  {                  return  owner          }  !        public  void  setOwner(String  owner)  {                  this.owner  =  owner          }  !        public  String  greet(String  name)  {                  return  "Hello  "  +  name  +  ",  I  am  "  +  owner          }  }  !Greeter  greeter  =  new  Greeter()  greeter.setOwner("Guillaume")  !System.out.println(greeter.greet("Marion"))

Parentheses

!30

@glaforge — @smaldini / #DV13-rtweb

Optional...

!31

public  class  Greeter  {          private  String  owner  !        public  String  getOwner()  {                  return  owner          }  !        public  void  setOwner(String  owner)  {                  this.owner  =  owner          }  !        public  String  greet(String  name)  {                  return  "Hello  "  +  name  +  ",  I  am  "  +  owner          }  }  !Greeter  greeter  =  new  Greeter()  greeter.setOwner  "Guillaume"  !System.out.println  greeter.greet("Marion")

@glaforge — @smaldini / #DV13-rtweb

Optional...

!31

public  class  Greeter  {          private  String  owner  !        public  String  getOwner()  {                  return  owner          }  !        public  void  setOwner(String  owner)  {                  this.owner  =  owner          }  !        public  String  greet(String  name)  {                  return  "Hello  "  +  name  +  ",  I  am  "  +  owner          }  }  !Greeter  greeter  =  new  Greeter()  greeter.setOwner  "Guillaume"  !System.out.println  greeter.greet("Marion")

return keyword

@glaforge — @smaldini / #DV13-rtweb

Optional...

!32

public  class  Greeter  {          private  String  owner  !        public  String  getOwner()  {                                owner          }  !        public  void  setOwner(String  owner)  {                  this.owner  =  owner          }  !        public  String  greet(String  name)  {                                "Hello  "  +  name  +  ",  I  am  "  +  owner          }  }  !Greeter  greeter  =  new  Greeter()  greeter.setOwner  "Guillaume"  !System.out.println  greeter.greet("Marion")

@glaforge — @smaldini / #DV13-rtweb

Optional...

!32

public  class  Greeter  {          private  String  owner  !        public  String  getOwner()  {                                owner          }  !        public  void  setOwner(String  owner)  {                  this.owner  =  owner          }  !        public  String  greet(String  name)  {                                "Hello  "  +  name  +  ",  I  am  "  +  owner          }  }  !Greeter  greeter  =  new  Greeter()  greeter.setOwner  "Guillaume"  !System.out.println  greeter.greet("Marion")

public keyword

@glaforge — @smaldini / #DV13-rtweb

Optional...

!33

             class  Greeter  {          private  String  owner  !                      String  getOwner()  {                                owner          }  !                      void  setOwner(String  owner)  {                  this.owner  =  owner          }  !                      String  greet(String  name)  {                                "Hello  "  +  name  +  ",  I  am  "  +  owner          }  }  !Greeter  greeter  =  new  Greeter()  greeter.setOwner  "Guillaume"  !System.out.println  greeter.greet("Marion")

@glaforge — @smaldini / #DV13-rtweb

Optional...

!33

             class  Greeter  {          private  String  owner  !                      String  getOwner()  {                                owner          }  !                      void  setOwner(String  owner)  {                  this.owner  =  owner          }  !                      String  greet(String  name)  {                                "Hello  "  +  name  +  ",  I  am  "  +  owner          }  }  !Greeter  greeter  =  new  Greeter()  greeter.setOwner  "Guillaume"  !System.out.println  greeter.greet("Marion")

optional typing

@glaforge — @smaldini / #DV13-rtweb

Optional...

!34

             class  Greeter  {          private  String  owner  !                      String  getOwner()  {                                owner          }  !                      void  setOwner(String  owner)  {                  this.owner  =  owner          }  !                      String  greet(String  name)  {                                "Hello  "  +  name  +  ",  I  am  "  +  owner          }  }  !def          greeter  =  new  Greeter()  greeter.setOwner  "Guillaume"  !System.out.println  greeter.greet("Marion")

@glaforge — @smaldini / #DV13-rtweb

Optional...

!34

             class  Greeter  {          private  String  owner  !                      String  getOwner()  {                                owner          }  !                      void  setOwner(String  owner)  {                  this.owner  =  owner          }  !                      String  greet(String  name)  {                                "Hello  "  +  name  +  ",  I  am  "  +  owner          }  }  !def          greeter  =  new  Greeter()  greeter.setOwner  "Guillaume"  !System.out.println  greeter.greet("Marion")

handy println shortcut

@glaforge — @smaldini / #DV13-rtweb

Optional...

!35

             class  Greeter  {          private  String  owner  !                      String  getOwner()  {                                owner          }  !                      void  setOwner(String  owner)  {                  this.owner  =  owner          }  !                      String  greet(String  name)  {                                "Hello  "  +  name  +  ",  I  am  "  +  owner          }  }  !def          greeter  =  new  Greeter()  greeter.setOwner  "Guillaume"  !                      println  greeter.greet("Marion")

@glaforge — @smaldini / #DV13-rtweb

Optional...

!35

             class  Greeter  {          private  String  owner  !                      String  getOwner()  {                                owner          }  !                      void  setOwner(String  owner)  {                  this.owner  =  owner          }  !                      String  greet(String  name)  {                                "Hello  "  +  name  +  ",  I  am  "  +  owner          }  }  !def          greeter  =  new  Greeter()  greeter.setOwner  "Guillaume"  !                      println  greeter.greet("Marion")

verbose Java properties!

@glaforge — @smaldini / #DV13-rtweb

Optional...

!36

             class  Greeter  {                          String  owner  !!!!!!!!!                      String  greet(String  name)  {                                "Hello  "  +  name  +  ",  I  am  "  +  owner          }  }  !def          greeter  =  new  Greeter()  greeter.setOwner  "Guillaume"  !                      println  greeter.greet("Marion")

@glaforge — @smaldini / #DV13-rtweb

Optional...

!36

             class  Greeter  {                          String  owner  !!!!!!!!!                      String  greet(String  name)  {                                "Hello  "  +  name  +  ",  I  am  "  +  owner          }  }  !def          greeter  =  new  Greeter()  greeter.setOwner  "Guillaume"  !                      println  greeter.greet("Marion")

Property notation

@glaforge — @smaldini / #DV13-rtweb

Optional...

!37

             class  Greeter  {                          String  owner  !!!!!!!!!                      String  greet(String  name)  {                                "Hello  "  +  name  +  ",  I  am  "  +  owner          }  }  !def          greeter  =  new  Greeter()  greeter.owner  =    "Guillaume"  !                      println  greeter.greet("Marion")

@glaforge — @smaldini / #DV13-rtweb

Optional...

!37

             class  Greeter  {                          String  owner  !!!!!!!!!                      String  greet(String  name)  {                                "Hello  "  +  name  +  ",  I  am  "  +  owner          }  }  !def          greeter  =  new  Greeter()  greeter.owner  =    "Guillaume"  !                      println  greeter.greet("Marion")

Named argumentconstructor

@glaforge — @smaldini / #DV13-rtweb

Optional...

!38

             class  Greeter  {                          String  owner  !!!!!!!!!                      String  greet(String  name)  {                                "Hello  "  +  name  +  ",  I  am  "  +  owner          }  }  !def          greeter  =  new  Greeter(owner:  "Guillaume")  !!                      println  greeter.greet("Marion")

@glaforge — @smaldini / #DV13-rtweb

Optional...

!38

             class  Greeter  {                          String  owner  !!!!!!!!!                      String  greet(String  name)  {                                "Hello  "  +  name  +  ",  I  am  "  +  owner          }  }  !def          greeter  =  new  Greeter(owner:  "Guillaume")  !!                      println  greeter.greet("Marion")

Interpolated strings!(aka GStrings)

@glaforge — @smaldini / #DV13-rtweb

Optional...

!39

             class  Greeter  {                          String  owner  !!!!!!!!!                      String  greet(String  name)  {                                "Hello  ${name},  I  am  ${owner}"          }  }  !def          greeter  =  new  Greeter(owner:  "Guillaume")  !!                      println  greeter.greet("Marion")

@glaforge — @smaldini / #DV13-rtweb

Optional...

!39

             class  Greeter  {                          String  owner  !!!!!!!!!                      String  greet(String  name)  {                                "Hello  ${name},  I  am  ${owner}"          }  }  !def          greeter  =  new  Greeter(owner:  "Guillaume")  !!                      println  greeter.greet("Marion")

Let’s reformat that messof whitespace!

@glaforge — @smaldini / #DV13-rtweb

Optional...

!40

class  Greeter  {          String  owner  !        String  greet(String  name)  {                "Hello  ${name},  I  am  ${owner}"          }  }  !def  greeter  =  new  Greeter(owner:  "Guillaume")  !println  greeter.greet("Marion")

@glaforge — @smaldini / #DV13-rtweb

Optional...

!40

class  Greeter  {          String  owner  !        String  greet(String  name)  {                "Hello  ${name},  I  am  ${owner}"          }  }  !def  greeter  =  new  Greeter(owner:  "Guillaume")  !println  greeter.greet("Marion")

public  class  Greeter  {          private  String  owner;  !        public  String  getOwner()  {                  return  owner;          }  !        public  void  setOwner(String  owner)  {                  this.owner  =  owner;          }  !        public  String  greet(String  name)  {                  return  "Hello  "  +  name  +  ",  I  am  "  +  owner;          }  }  !Greeter  greeter  =  new  Greeter();  greeter.setOwner("Guillaume");  !System.out.println(greeter.greet("Marion"));

@glaforge — @smaldini / #DV13-rtweb

Optional...

!40

class  Greeter  {          String  owner  !        String  greet(String  name)  {                "Hello  ${name},  I  am  ${owner}"          }  }  !def  greeter  =  new  Greeter(owner:  "Guillaume")  !println  greeter.greet("Marion")

@glaforge — @smaldini / #DV13-rtweb

Native syntax constructs

!41

//  closures  def  adder  =  {  a,  b  -­‐>  a  +  b  }  !

//  lists  def  list  =  [1,  2,  3,  4,  5]  !

//  maps  def  map  =  [a:  1,  b:  2,  c:  3]  !

//  regular  expressions  def  regex  =  ~/.*foo.*/  !

//  ranges  def  range  128..255

@glaforge — @smaldini / #DV13-rtweb

Closures — the basics

• Functions as first-class citizen of the language

!42

def  adder  =  {  a,  b  -­‐>  a  +  b  }  !

assert  adder(1,  2)  ==  3  assert  adder('a',  'b')  ==  'ab'

@glaforge — @smaldini / #DV13-rtweb

Closures — the basics

• Functions as first-class citizen of the language

!42

def  adder  =  {  a,  b  -­‐>  a  +  b  }  !

assert  adder(1,  2)  ==  3  assert  adder('a',  'b')  ==  'ab'

Closureparameters

@glaforge — @smaldini / #DV13-rtweb

Closures — the basics

• Functions as first-class citizen of the language

!42

def  adder  =  {  a,  b  -­‐>  a  +  b  }  !

assert  adder(1,  2)  ==  3  assert  adder('a',  'b')  ==  'ab'

Assign a functioninto a variable

Closureparameters

@glaforge — @smaldini / #DV13-rtweb

Closures — the basics

• Functions as first-class citizen of the language

!42

def  adder  =  {  a,  b  -­‐>  a  +  b  }  !

assert  adder(1,  2)  ==  3  assert  adder('a',  'b')  ==  'ab'

Short form of:adder.call(‘a’, ‘b’)

Assign a functioninto a variable

Closureparameters

@glaforge — @smaldini / #DV13-rtweb

Closures — the basics

• Functions as first-class citizen of the language

!42

def  adder  =  {  a,  b  -­‐>  a  +  b  }  !

assert  adder(1,  2)  ==  3  assert  adder('a',  'b')  ==  'ab'

Short form of:adder.call(‘a’, ‘b’)

Genericity withduck typing & operator

overloading

Assign a functioninto a variable

Closureparameters

@glaforge — @smaldini / #DV13-rtweb

Closures — explicit type

!43

!

def  intAdder  =  {  int  a,  int  b  -­‐>  a  +  b  }

@glaforge — @smaldini / #DV13-rtweb

Closures — explicit type

!43

!

def  intAdder  =  {  int  a,  int  b  -­‐>  a  +  b  }

Be explicit aboutthe types

@glaforge — @smaldini / #DV13-rtweb

Closures — implicit parameter

!44

def  doubler  =  {  it  *  2  }  !

assert  doubler(3)  ==  6  assert  doubler('a')  ==  'aa'

@glaforge — @smaldini / #DV13-rtweb

Closures — implicit parameter

!44

def  doubler  =  {  it  *  2  }  !

assert  doubler(3)  ==  6  assert  doubler('a')  ==  'aa'

Implicitparameter

@glaforge — @smaldini / #DV13-rtweb

Closures — implicit parameter

!44

def  doubler  =  {  it  *  2  }  !

assert  doubler(3)  ==  6  assert  doubler('a')  ==  'aa'

Implicitparameter

Multiply also definedon strings

@glaforge — @smaldini / #DV13-rtweb

Closures — variable arguments

!45

def  sum  =  {  ...  elements  -­‐>                                  elements.sum()  }  !

assert  sum(1,  2)  ==  3  assert  sum('a',  'b',  'c')  ==  'abc'

@glaforge — @smaldini / #DV13-rtweb

Closures — variable arguments

!45

def  sum  =  {  ...  elements  -­‐>                                  elements.sum()  }  !

assert  sum(1,  2)  ==  3  assert  sum('a',  'b',  'c')  ==  'abc'

Variable number ofarguments

@glaforge — @smaldini / #DV13-rtweb

Closures — variable arguments

!45

def  sum  =  {  ...  elements  -­‐>                                  elements.sum()  }  !

assert  sum(1,  2)  ==  3  assert  sum('a',  'b',  'c')  ==  'abc'

You can specify thetype: int...

Variable number ofarguments

@glaforge — @smaldini / #DV13-rtweb

Closures — default values

!46

def  mult  =  {  int  a,  int  b  =  10  -­‐>  a  *  b  }  !

assert  mult(2,  3)  ==  6  assert  mult(5)  ==  50

@glaforge — @smaldini / #DV13-rtweb

Closures — default values

!46

def  mult  =  {  int  a,  int  b  =  10  -­‐>  a  *  b  }  !

assert  mult(2,  3)  ==  6  assert  mult(5)  ==  50

Default value

@glaforge — @smaldini / #DV13-rtweb

Closures — default values

!46

def  mult  =  {  int  a,  int  b  =  10  -­‐>  a  *  b  }  !

assert  mult(2,  3)  ==  6  assert  mult(5)  ==  50

Default value

Provided valuefor b

@glaforge — @smaldini / #DV13-rtweb

Closures — default values

!46

def  mult  =  {  int  a,  int  b  =  10  -­‐>  a  *  b  }  !

assert  mult(2,  3)  ==  6  assert  mult(5)  ==  50

Default value

Provided valuefor b

Default valueused for b

@glaforge — @smaldini / #DV13-rtweb

Closures — methods as functions

!47

def  logBase10  =  Math.&log10  def  printer  =  System.out.&println  !

assert  logBase10(10)  ==  1  printer  'abc'

@glaforge — @smaldini / #DV13-rtweb

Closures — methods as functions

!47

def  logBase10  =  Math.&log10  def  printer  =  System.out.&println  !

assert  logBase10(10)  ==  1  printer  'abc'

Turn a method into a closure function

@glaforge — @smaldini / #DV13-rtweb

Closures — map / filter / reduce

!48

@glaforge — @smaldini / #DV13-rtweb

Closures — map / filter / reduce

!48

@groovy.transform.Immutableclass  Person  {        String  name        int  age}

@glaforge — @smaldini / #DV13-rtweb

Closures — map / filter / reduce

!48

@groovy.transform.Immutableclass  Person  {        String  name        int  age}

@glaforge — @smaldini / #DV13-rtweb

Closures — map / filter / reduce

!48

@groovy.transform.Immutableclass  Person  {        String  name        int  age}

def  persons  =  [        new  Person('Guillaume',  36),        new  Person('Marion',  5),        new  Person('Erine',  1)]

@glaforge — @smaldini / #DV13-rtweb

Closures — map / filter / reduce

!48

@groovy.transform.Immutableclass  Person  {        String  name        int  age}

def  persons  =  [        new  Person('Guillaume',  36),        new  Person('Marion',  5),        new  Person('Erine',  1)]

@glaforge — @smaldini / #DV13-rtweb

Closures — map / filter / reduce

!48

@groovy.transform.Immutableclass  Person  {        String  name        int  age}

def  persons  =  [        new  Person('Guillaume',  36),        new  Person('Marion',  5),        new  Person('Erine',  1)]

def  names  =        persons.findAll  {  it.age  <  18  }                      .collect  {  it.name.toUpperCase()  }                      .sort()                      .join(',  ')

@glaforge — @smaldini / #DV13-rtweb

Closures — map / filter / reduce

!48

@groovy.transform.Immutableclass  Person  {        String  name        int  age}

def  persons  =  [        new  Person('Guillaume',  36),        new  Person('Marion',  5),        new  Person('Erine',  1)]

def  names  =        persons.findAll  {  it.age  <  18  }                      .collect  {  it.name.toUpperCase()  }                      .sort()                      .join(',  ')

@glaforge — @smaldini / #DV13-rtweb

Closures — map / filter / reduce

!48

@groovy.transform.Immutableclass  Person  {        String  name        int  age}

def  persons  =  [        new  Person('Guillaume',  36),        new  Person('Marion',  5),        new  Person('Erine',  1)]

def  names  =        persons.findAll  {  it.age  <  18  }                      .collect  {  it.name.toUpperCase()  }                      .sort()                      .join(',  ')

assert  names  ==  "ERINE,  MARION"

@glaforge — @smaldini / #DV13-rtweb

Closures — map / filter / reduce

!48

@groovy.transform.Immutableclass  Person  {        String  name        int  age}

def  persons  =  [        new  Person('Guillaume',  36),        new  Person('Marion',  5),        new  Person('Erine',  1)]

def  names  =        persons.findAll  {  it.age  <  18  }                      .collect  {  it.name.toUpperCase()  }                      .sort()                      .join(',  ')

assert  names  ==  "ERINE,  MARION"

find/findAll, inject, collect, flatten, min/max, unique, reverse, collate, groupBy, any/every, head/tail/last,

count/countBy, combinations/permutations/subsequences/

transpose, withDefault/withLazyDefault

@glaforge — @smaldini / #DV13-rtweb

Closures — resource handling

!49

new  File('bible.txt').withReader  {  r  -­‐>          new  File('out.txt').withWriter  {  w  -­‐>                  r.eachLine  {  line  -­‐>                          if  (line.contains('Groovy'))                                  w  <<  line.toUpperCase()                  }          }  }

@glaforge — @smaldini / #DV13-rtweb

Closures — resource handling

!49

new  File('bible.txt').withReader  {  r  -­‐>          new  File('out.txt').withWriter  {  w  -­‐>                  r.eachLine  {  line  -­‐>                          if  (line.contains('Groovy'))                                  w  <<  line.toUpperCase()                  }          }  }

Take care of properly opening /closing resources

@glaforge — @smaldini / #DV13-rtweb

Closures — custom control structures

!50

void  unless(boolean  cond,  Closure  c)  {          if  (!cond)  c()  }  !

unless  (10  <  9)  {          println  "less"  }

@glaforge — @smaldini / #DV13-rtweb

Closures — custom control structures

!50

void  unless(boolean  cond,  Closure  c)  {          if  (!cond)  c()  }  !

unless  (10  <  9)  {          println  "less"  }

Closure as last argument

@glaforge — @smaldini / #DV13-rtweb

Closures — custom control structures

!50

void  unless(boolean  cond,  Closure  c)  {          if  (!cond)  c()  }  !

unless  (10  <  9)  {          println  "less"  }

Closure as last argument

Equivalent to:unless(10<9, {...})

@glaforge — @smaldini / #DV13-rtweb

Closures — custom control structures

!51

class  Reactor  {            void  on(Selector  s,  Closure  c)  {  …  }  }  …  reactor.on(  $(‘abc')  )  {  Event<String>  ev  -­‐>          …  }

@glaforge — @smaldini / #DV13-rtweb

Closures — builders leverage closures

!52

import  groovy.json.*  !

def  json  =  new  JsonBuilder()  json.person  {          name  'Guillaume'          age  36          daughters  'Marion',  'Erine'          address  {                  street  '1  Main  Street'                  zip  75001                  city  'Paris'          }  }

@glaforge — @smaldini / #DV13-rtweb

Closures — builders leverage closures

!52

import  groovy.json.*  !

def  json  =  new  JsonBuilder()  json.person  {          name  'Guillaume'          age  36          daughters  'Marion',  'Erine'          address  {                  street  '1  Main  Street'                  zip  75001                  city  'Paris'          }  }

Hierarchical datarepresentation

@glaforge — @smaldini / #DV13-rtweb

Closures — builders leverage closures

!52

import  groovy.json.*  !

def  json  =  new  JsonBuilder()  json.person  {          name  'Guillaume'          age  36          daughters  'Marion',  'Erine'          address  {                  street  '1  Main  Street'                  zip  75001                  city  'Paris'          }  }

Hierarchical datarepresentation

Closure blocksdelimiting the

structure

@glaforge — @smaldini / #DV13-rtweb

Closures — builders leverage closures

!52

import  groovy.json.*  !

def  json  =  new  JsonBuilder()  json.person  {          name  'Guillaume'          age  36          daughters  'Marion',  'Erine'          address  {                  street  '1  Main  Street'                  zip  75001                  city  'Paris'          }  }

Hierarchical datarepresentation

Closure blocksdelimiting the

structure

{          "person":  {                  "name":  "Guillaume",                  "age":  36,                  "daughters":  [                          "Marion",                          "Erine"                  ],                  "address":  {                          "street":  "1  Main  Street",                          "zip":  75001,                          "city":  "Paris"                  }          }  }

@glaforge — @smaldini / #DV13-rtweb

Closure — your own builders

!53

GroovyEnvironment.create  {          environment  {                  defaultDispatcher  =  "test"  !

               dispatcher('test')  {                          type  =  DispatcherType.SYNCHRONOUS                  }          }  }

@glaforge — @smaldini / #DV13-rtweb

Closure — your own builders

!53

GroovyEnvironment.create  {          environment  {                  defaultDispatcher  =  "test"  !

               dispatcher('test')  {                          type  =  DispatcherType.SYNCHRONOUS                  }          }  }

@glaforge — @smaldini / #DV13-rtweb

Closure — your own builders

!53

GroovyEnvironment.create  {          environment  {                  defaultDispatcher  =  "test"  !

               dispatcher('test')  {                          type  =  DispatcherType.SYNCHRONOUS                  }          }  }

@glaforge — @smaldini / #DV13-rtweb

Closure — your own builders

!53

GroovyEnvironment.create  {          environment  {                  defaultDispatcher  =  "test"  !

               dispatcher('test')  {                          type  =  DispatcherType.SYNCHRONOUS                  }          }  }

@glaforge — @smaldini / #DV13-rtweb

Closure — your own builders

!53

GroovyEnvironment.create  {          environment  {                  defaultDispatcher  =  "test"  !

               dispatcher('test')  {                          type  =  DispatcherType.SYNCHRONOUS                  }          }  }

@glaforge — @smaldini / #DV13-rtweb

Closure — your own builders

!53

GroovyEnvironment.create  {          environment  {                  defaultDispatcher  =  "test"  !

               dispatcher('test')  {                          type  =  DispatcherType.SYNCHRONOUS                  }          }  }

Implement a builder extendingBuilderSupport or FactoryBuilderSupport…

or roll your own!

@glaforge — @smaldini / #DV13-rtweb

Closure — your own builders

!53

GroovyEnvironment.create  {          environment  {                  defaultDispatcher  =  "test"  !

               dispatcher('test')  {                          type  =  DispatcherType.SYNCHRONOUS                  }          }  }

abstract  class  BuilderSupport  {          abstract  void  setParent(Object  parent,  Object  child)          abstract  Object  createNode(Object  name)          abstract  Object  createNode(Object  name,  Object  value)          abstract  Object  createNode(Object  name,  Map  attributes)          abstract  Object  createNode(Object  name,  Map  attributes,  Object  value)  }  

Implement a builder extendingBuilderSupport or FactoryBuilderSupport…

or roll your own!

@glaforge — @smaldini / #DV13-rtweb

GPath expressions

• GPath expressions are like XPath but for an object graph

!54

import  groovy.json.*  !

def  url  =  "https://api.github.com/repos/groovy/groovy-­‐core/commits"  !

def  commits  =  new  JsonSlurper().parseText(url.toURL().text)  !

assert  commits[0].commit.author.name  ==  'Cedric  Champeau'

@glaforge — @smaldini / #DV13-rtweb

GPath expressions

• GPath expressions are like XPath but for an object graph

!54

import  groovy.json.*  !

def  url  =  "https://api.github.com/repos/groovy/groovy-­‐core/commits"  !

def  commits  =  new  JsonSlurper().parseText(url.toURL().text)  !

assert  commits[0].commit.author.name  ==  'Cedric  Champeau'

GPath expression

@glaforge — @smaldini / #DV13-rtweb

GPath expressions

• GPath expressions are like XPath but for an object graph

!54

import  groovy.json.*  !

def  url  =  "https://api.github.com/repos/groovy/groovy-­‐core/commits"  !

def  commits  =  new  JsonSlurper().parseText(url.toURL().text)  !

assert  commits[0].commit.author.name  ==  'Cedric  Champeau'

GPath expression Add find / findAllinto the mix

@glaforge — @smaldini / #DV13-rtweb

GPath expressions

• GPath expressions are like XPath but for an object graph

!54

import  groovy.json.*  !

def  url  =  "https://api.github.com/repos/groovy/groovy-­‐core/commits"  !

def  commits  =  new  JsonSlurper().parseText(url.toURL().text)  !

assert  commits[0].commit.author.name  ==  'Cedric  Champeau'

GPath expression Add find / findAllinto the mix

No (un)marshalling!

@glaforge — @smaldini / #DV13-rtweb

Lists

!55

def  list  =  ['a',  'b',  'c']  !

list  <<  'd'  assert  list.contains('d')  !

assert  list.findAll  {  it.startsWith  'a'  }.size()  ==  1  assert  list.collect  {  it.toUpperCase()  }                                                                ==  ['A',  'B',  'C',  'D']  assert  list.inject('')  {  a,  b  -­‐>  a  +  b  }  ==  'abcd'

@glaforge — @smaldini / #DV13-rtweb

Lists

!55

def  list  =  ['a',  'b',  'c']  !

list  <<  'd'  assert  list.contains('d')  !

assert  list.findAll  {  it.startsWith  'a'  }.size()  ==  1  assert  list.collect  {  it.toUpperCase()  }                                                                ==  ['A',  'B',  'C',  'D']  assert  list.inject('')  {  a,  b  -­‐>  a  +  b  }  ==  'abcd'

List definition

@glaforge — @smaldini / #DV13-rtweb

Lists

!55

def  list  =  ['a',  'b',  'c']  !

list  <<  'd'  assert  list.contains('d')  !

assert  list.findAll  {  it.startsWith  'a'  }.size()  ==  1  assert  list.collect  {  it.toUpperCase()  }                                                                ==  ['A',  'B',  'C',  'D']  assert  list.inject('')  {  a,  b  -­‐>  a  +  b  }  ==  'abcd'

List definition

Append an element(operator overloading)

@glaforge — @smaldini / #DV13-rtweb

Lists

!55

def  list  =  ['a',  'b',  'c']  !

list  <<  'd'  assert  list.contains('d')  !

assert  list.findAll  {  it.startsWith  'a'  }.size()  ==  1  assert  list.collect  {  it.toUpperCase()  }                                                                ==  ['A',  'B',  'C',  'D']  assert  list.inject('')  {  a,  b  -­‐>  a  +  b  }  ==  'abcd'

List definition

Append an element(operator overloading)

Functional-style map /filter / reduce with

closures

@glaforge — @smaldini / #DV13-rtweb

Maps

!56

def  map  =  [name:  'Guillaume',  age:  36]  !

map.daughters  =  ['Marion',  'Erine']  !

assert  map['daughters'].contains('Marion')

@glaforge — @smaldini / #DV13-rtweb

Maps

!56

def  map  =  [name:  'Guillaume',  age:  36]  !

map.daughters  =  ['Marion',  'Erine']  !

assert  map['daughters'].contains('Marion')

Map definition

@glaforge — @smaldini / #DV13-rtweb

Maps

!56

def  map  =  [name:  'Guillaume',  age:  36]  !

map.daughters  =  ['Marion',  'Erine']  !

assert  map['daughters'].contains('Marion')

Map definition

Indexed access

@glaforge — @smaldini / #DV13-rtweb

Maps

!56

def  map  =  [name:  'Guillaume',  age:  36]  !

map.daughters  =  ['Marion',  'Erine']  !

assert  map['daughters'].contains('Marion')

Map definition

Indexed accessProperty notationaccess

@glaforge — @smaldini / #DV13-rtweb

Regular expressions

!57

def  pattern  =  ~/.*foo.*/  !

assert  "Alibaba"  ==~  /.*(ba){2}/  !

def  matcher  =  "Superman"  =~  /([A-­‐Z][a-­‐z]+)man/  assert  matcher[0][0]  ==  'Superman'  assert  matcher[0][1]  ==  'Super'  !

'75001  Paris'.find(/(\d{5})\s(\w+)/)  {  match,  zip,  town  -­‐>          println  "The  Zip  code  of  ${town}  is  ${zip}"  }

@glaforge — @smaldini / #DV13-rtweb

Regular expressions

!57

def  pattern  =  ~/.*foo.*/  !

assert  "Alibaba"  ==~  /.*(ba){2}/  !

def  matcher  =  "Superman"  =~  /([A-­‐Z][a-­‐z]+)man/  assert  matcher[0][0]  ==  'Superman'  assert  matcher[0][1]  ==  'Super'  !

'75001  Paris'.find(/(\d{5})\s(\w+)/)  {  match,  zip,  town  -­‐>          println  "The  Zip  code  of  ${town}  is  ${zip}"  }

Pattern

@glaforge — @smaldini / #DV13-rtweb

Regular expressions

!57

def  pattern  =  ~/.*foo.*/  !

assert  "Alibaba"  ==~  /.*(ba){2}/  !

def  matcher  =  "Superman"  =~  /([A-­‐Z][a-­‐z]+)man/  assert  matcher[0][0]  ==  'Superman'  assert  matcher[0][1]  ==  'Super'  !

'75001  Paris'.find(/(\d{5})\s(\w+)/)  {  match,  zip,  town  -­‐>          println  "The  Zip  code  of  ${town}  is  ${zip}"  }

PatternMatch

@glaforge — @smaldini / #DV13-rtweb

Regular expressions

!57

def  pattern  =  ~/.*foo.*/  !

assert  "Alibaba"  ==~  /.*(ba){2}/  !

def  matcher  =  "Superman"  =~  /([A-­‐Z][a-­‐z]+)man/  assert  matcher[0][0]  ==  'Superman'  assert  matcher[0][1]  ==  'Super'  !

'75001  Paris'.find(/(\d{5})\s(\w+)/)  {  match,  zip,  town  -­‐>          println  "The  Zip  code  of  ${town}  is  ${zip}"  }

PatternMatch

Find

@glaforge — @smaldini / #DV13-rtweb

Regular expressions

!57

def  pattern  =  ~/.*foo.*/  !

assert  "Alibaba"  ==~  /.*(ba){2}/  !

def  matcher  =  "Superman"  =~  /([A-­‐Z][a-­‐z]+)man/  assert  matcher[0][0]  ==  'Superman'  assert  matcher[0][1]  ==  'Super'  !

'75001  Paris'.find(/(\d{5})\s(\w+)/)  {  match,  zip,  town  -­‐>          println  "The  Zip  code  of  ${town}  is  ${zip}"  }

PatternMatch

Find

Nice way to decompose the matched regions

@glaforge — @smaldini / #DV13-rtweb

Ranges

!58

def  range  =  'a'..'z'  !

assert  range.contains('m')  assert  range.contains('z')  !

def  exclusive  =  1..<10  !

assert  !exclusive.contains(10)  !

def  reverse  =  10..0  !

assert  reverse[0]  ==  10  assert  reverse[-­‐1]  ==  0

@glaforge — @smaldini / #DV13-rtweb

Ranges

!58

def  range  =  'a'..'z'  !

assert  range.contains('m')  assert  range.contains('z')  !

def  exclusive  =  1..<10  !

assert  !exclusive.contains(10)  !

def  reverse  =  10..0  !

assert  reverse[0]  ==  10  assert  reverse[-­‐1]  ==  0

Range

@glaforge — @smaldini / #DV13-rtweb

Ranges

!58

def  range  =  'a'..'z'  !

assert  range.contains('m')  assert  range.contains('z')  !

def  exclusive  =  1..<10  !

assert  !exclusive.contains(10)  !

def  reverse  =  10..0  !

assert  reverse[0]  ==  10  assert  reverse[-­‐1]  ==  0

Range

Excluded upper bound

@glaforge — @smaldini / #DV13-rtweb

Ranges

!58

def  range  =  'a'..'z'  !

assert  range.contains('m')  assert  range.contains('z')  !

def  exclusive  =  1..<10  !

assert  !exclusive.contains(10)  !

def  reverse  =  10..0  !

assert  reverse[0]  ==  10  assert  reverse[-­‐1]  ==  0

Range

Excluded upper bound

Reverse range

@glaforge — @smaldini / #DV13-rtweb

Ranges

!58

def  range  =  'a'..'z'  !

assert  range.contains('m')  assert  range.contains('z')  !

def  exclusive  =  1..<10  !

assert  !exclusive.contains(10)  !

def  reverse  =  10..0  !

assert  reverse[0]  ==  10  assert  reverse[-­‐1]  ==  0

Range

Excluded upper bound

Reverse range

Negative index countfrom the end

@glaforge — @smaldini / #DV13-rtweb

Strings, GStrings, multiline strings

!59

def  name  =  'Groovy'  def  tmpl  =  """          Dear  Mr  ${name},          You're  the  winner  of  the  lottery!          Yours  sincerly,          Dave  """  !

assert  tmpl.toString().contains('Groovy')

@glaforge — @smaldini / #DV13-rtweb

Strings, GStrings, multiline strings

!59

def  name  =  'Groovy'  def  tmpl  =  """          Dear  Mr  ${name},          You're  the  winner  of  the  lottery!          Yours  sincerly,          Dave  """  !

assert  tmpl.toString().contains('Groovy')

Plain java.lang.String

@glaforge — @smaldini / #DV13-rtweb

Strings, GStrings, multiline strings

!59

def  name  =  'Groovy'  def  tmpl  =  """          Dear  Mr  ${name},          You're  the  winner  of  the  lottery!          Yours  sincerly,          Dave  """  !

assert  tmpl.toString().contains('Groovy')

Plain java.lang.String

Multiline string withexpression interpolation

@glaforge — @smaldini / #DV13-rtweb

Surprising numbers...

!60

System.out.println(  2.0  -­‐  1.1  );

@glaforge — @smaldini / #DV13-rtweb

Surprising numbers...

!60

System.out.println(  2.0  -­‐  1.1  );

0.8999999999999999

@glaforge — @smaldini / #DV13-rtweb

Surprising numbers...

!60

System.out.println(  2.0  -­‐  1.1  );

0.8999999999999999

@glaforge — @smaldini / #DV13-rtweb

Surprising numbers...

!61

System.out.println(  3  /  2  );

@glaforge — @smaldini / #DV13-rtweb

Surprising numbers...

!61

System.out.println(  3  /  2  );

1

@glaforge — @smaldini / #DV13-rtweb

Surprising numbers...

!61

System.out.println(  3  /  2  );

1

@glaforge — @smaldini / #DV13-rtweb

BigDecimal by default!

!62

assert  2.0  -­‐  1.1  ==  0.9

assert  3  /  2  ==  1.5

@glaforge — @smaldini / #DV13-rtweb

BigDecimal by default!

!62

assert  2.0  -­‐  1.1  ==  0.9

assert  3  /  2  ==  1.5

One of the reasons why micro-benchmarks sometimes showed

Groovy to be slow...

@glaforge — @smaldini / #DV13-rtweb

BigDecimal by default!

!62

assert  2.0  -­‐  1.1  ==  0.9

assert  3  /  2  ==  1.5

One of the reasons why micro-benchmarks sometimes showed

Groovy to be slow...

But you can use doubles & floats forperformance, with ‘d’ or ‘f ’ suffixes

or with explicit type

Powerful switch /case on steroids

Powerful switch /case on steroids

switch(obj)  {          case  123:                  "number  123";              break          case  "abc":              "string  abc";              break          case  String:            "is  a  string";            break          case  [1,  2,  3]:      "in  list";                    break          case  ~/.*o+.*/:      "regex  match";            break          case  {  it  <  3  }:    "closure  criteria";  break          default:                    "unknown"  }

@glaforge — @smaldini / #DV13-rtweb

Named arguments

!64

move  obj,  x:  3,  y:  4

@glaforge — @smaldini / #DV13-rtweb

Named arguments

!64

move  obj,  x:  3,  y:  4

Normal argument

@glaforge — @smaldini / #DV13-rtweb

Named arguments

!64

move  obj,  x:  3,  y:  4

Normal argument Named argument

@glaforge — @smaldini / #DV13-rtweb

Named arguments

!64

move  obj,  x:  3,  y:  4

Normal argument Named argument

Calls:move(Map m, Object)

@glaforge — @smaldini / #DV13-rtweb

Command chains

• Ability to chain method calls without parentheses and dots

!65

move  forward  at  3.km/h

@glaforge — @smaldini / #DV13-rtweb

Command chains

• Ability to chain method calls without parentheses and dots

!65

move  forward  at  3.km/h

Actually equivalent to:move(forward).at(3.getKm().div(h))

@glaforge — @smaldini / #DV13-rtweb

Named arguments & command chains

!66

check  that:  vodka  tastes  good

@glaforge — @smaldini / #DV13-rtweb

Named arguments & command chains

!66

check  that:  vodka  tastes  good

Will call:check(that: vodka).tastes(good)

@glaforge — @smaldini / #DV13-rtweb

Multiple assignment and destructuring

!67

def  (a,  b)  =  ['A',  'B']  !

(a,  b)  =  [b,  a]  !

def  (int  i,  int  j)  =  [1,  2]  !

def  geocode(String  place)  {          return  [45.4,  2.3]  }  !

def  (la,  lo)  =  geocode("Paris")  !

assert  la  ==  45.4  &&  lo  ==  2.3

@glaforge — @smaldini / #DV13-rtweb

Multiple assignment and destructuring

!67

def  (a,  b)  =  ['A',  'B']  !

(a,  b)  =  [b,  a]  !

def  (int  i,  int  j)  =  [1,  2]  !

def  geocode(String  place)  {          return  [45.4,  2.3]  }  !

def  (la,  lo)  =  geocode("Paris")  !

assert  la  ==  45.4  &&  lo  ==  2.3

Classic « swap »

@glaforge — @smaldini / #DV13-rtweb

Multiple assignment and destructuring

!67

def  (a,  b)  =  ['A',  'B']  !

(a,  b)  =  [b,  a]  !

def  (int  i,  int  j)  =  [1,  2]  !

def  geocode(String  place)  {          return  [45.4,  2.3]  }  !

def  (la,  lo)  =  geocode("Paris")  !

assert  la  ==  45.4  &&  lo  ==  2.3

Classic « swap »With types

@glaforge — @smaldini / #DV13-rtweb

Multiple assignment and destructuring

!67

def  (a,  b)  =  ['A',  'B']  !

(a,  b)  =  [b,  a]  !

def  (int  i,  int  j)  =  [1,  2]  !

def  geocode(String  place)  {          return  [45.4,  2.3]  }  !

def  (la,  lo)  =  geocode("Paris")  !

assert  la  ==  45.4  &&  lo  ==  2.3

Classic « swap »With types

Methodreturning a list

@glaforge — @smaldini / #DV13-rtweb

Multiple assignment and destructuring

!67

def  (a,  b)  =  ['A',  'B']  !

(a,  b)  =  [b,  a]  !

def  (int  i,  int  j)  =  [1,  2]  !

def  geocode(String  place)  {          return  [45.4,  2.3]  }  !

def  (la,  lo)  =  geocode("Paris")  !

assert  la  ==  45.4  &&  lo  ==  2.3

Classic « swap »With types

Methodreturning a list

Destructuring

@glaforge — @smaldini / #DV13-rtweb

Multiple assignment and destructuring

!68

class  Point  {          double  x,  y  !

       double  getAt(int  idx)  {                  if  (idx  ==  0)  x                  else  if  (idx  ==  1)  y                  else  throw  new  Exception("Wrong  index")          }  }  !

def  (x,  y)  =  new  Point(x:  48.3,  y:  3.5)  !

assert  x  ==  48.3  &&  y  ==  3.5

@glaforge — @smaldini / #DV13-rtweb

Multiple assignment and destructuring

!68

class  Point  {          double  x,  y  !

       double  getAt(int  idx)  {                  if  (idx  ==  0)  x                  else  if  (idx  ==  1)  y                  else  throw  new  Exception("Wrong  index")          }  }  !

def  (x,  y)  =  new  Point(x:  48.3,  y:  3.5)  !

assert  x  ==  48.3  &&  y  ==  3.5

Method signatureconvention: getAt(int)

@glaforge — @smaldini / #DV13-rtweb

Multiple assignment and destructuring

!68

class  Point  {          double  x,  y  !

       double  getAt(int  idx)  {                  if  (idx  ==  0)  x                  else  if  (idx  ==  1)  y                  else  throw  new  Exception("Wrong  index")          }  }  !

def  (x,  y)  =  new  Point(x:  48.3,  y:  3.5)  !

assert  x  ==  48.3  &&  y  ==  3.5

Method signatureconvention: getAt(int)

Transparent destructuring

@glaforge — @smaldini / #DV13-rtweb

Operator overloading

!69

import  static  Unit.*  !enum  Unit  {      M(1),  KM(1000)  !    double  mult      Unit(double  mult)  {          this.mult  =  mult      }  }

@glaforge — @smaldini / #DV13-rtweb

Operator overloading

!69

import  static  Unit.*  !enum  Unit  {      M(1),  KM(1000)  !    double  mult      Unit(double  mult)  {          this.mult  =  mult      }  }

import  groovy.transform.Immutable  import  static  Unit.*  !@Immutable  class  Distance  {      double  q      Unit  u  !    Distance  plus(Distance  d)  {          new  Distance(    q  *      u.mult  +                                    d.q  *  d.u.mult,  M)      }  }

@glaforge — @smaldini / #DV13-rtweb

Operator overloading

!69

import  static  Unit.*  !enum  Unit  {      M(1),  KM(1000)  !    double  mult      Unit(double  mult)  {          this.mult  =  mult      }  }

import  groovy.transform.Immutable  import  static  Unit.*  !@Immutable  class  Distance  {      double  q      Unit  u  !    Distance  plus(Distance  d)  {          new  Distance(    q  *      u.mult  +                                    d.q  *  d.u.mult,  M)      }  }

import  static  Unit.*  !def  d10  =  new  Distance(10,  M)  def  d15  =  new  Distance(15,  M)  !def  d25  =  d10  +  d15  !assert  d25.q  ==  25  &&  d25.u  ==  M

@glaforge — @smaldini / #DV13-rtweb

Operator overloading

!69

import  static  Unit.*  !enum  Unit  {      M(1),  KM(1000)  !    double  mult      Unit(double  mult)  {          this.mult  =  mult      }  }

import  groovy.transform.Immutable  import  static  Unit.*  !@Immutable  class  Distance  {      double  q      Unit  u  !    Distance  plus(Distance  d)  {          new  Distance(    q  *      u.mult  +                                    d.q  *  d.u.mult,  M)      }  }

import  static  Unit.*  !def  d10  =  new  Distance(10,  M)  def  d15  =  new  Distance(15,  M)  !def  d25  =  d10  +  d15  !assert  d25.q  ==  25  &&  d25.u  ==  M

Overload the ‘+’operator

@glaforge — @smaldini / #DV13-rtweb

Operator overloading

!69

import  static  Unit.*  !enum  Unit  {      M(1),  KM(1000)  !    double  mult      Unit(double  mult)  {          this.mult  =  mult      }  }

import  groovy.transform.Immutable  import  static  Unit.*  !@Immutable  class  Distance  {      double  q      Unit  u  !    Distance  plus(Distance  d)  {          new  Distance(    q  *      u.mult  +                                    d.q  *  d.u.mult,  M)      }  }

import  static  Unit.*  !def  d10  =  new  Distance(10,  M)  def  d15  =  new  Distance(15,  M)  !def  d25  =  d10  +  d15  !assert  d25.q  ==  25  &&  d25.u  ==  M

Overload the ‘+’operator

Use ‘+’

@glaforge — @smaldini / #DV13-rtweb

Operator overloading

!70

a  +  b      //  a.plus(b)  a  -­‐  b      //  a.minus(b)  a  *  b      //  a.multiply(b)  a  /  b      //  a.div(b)  a  %  b      //  a.modulo(b)  a  **  b    //  a.power(b)  a  |  b      //  a.or(b)  a  &  b      //  a.and(b)  a  ^  b      //  a.xor(b)  a[b]        //  a.getAt(b)  a  <<  b    //  a.leftShift(b)  a  >>  b    //  a.rightShift(b)  a  >>>  b  //  a.rightShiftUnsigned(b)  +a            //  a.unaryPlus()  -­‐a            //  a.unaryMinus()  ~a            //  a.bitwiseNegate()

@glaforge — @smaldini / #DV13-rtweb

Operator overloading

!70

a  +  b      //  a.plus(b)  a  -­‐  b      //  a.minus(b)  a  *  b      //  a.multiply(b)  a  /  b      //  a.div(b)  a  %  b      //  a.modulo(b)  a  **  b    //  a.power(b)  a  |  b      //  a.or(b)  a  &  b      //  a.and(b)  a  ^  b      //  a.xor(b)  a[b]        //  a.getAt(b)  a  <<  b    //  a.leftShift(b)  a  >>  b    //  a.rightShift(b)  a  >>>  b  //  a.rightShiftUnsigned(b)  +a            //  a.unaryPlus()  -­‐a            //  a.unaryMinus()  ~a            //  a.bitwiseNegate()

Operator overloading isjust a convention on

method names

@glaforge — @smaldini / #DV13-rtweb

Power asserts

!71

def  (a,  b,  c)  =  [20,  30,  40]  !

assert  a  *  (b  -­‐  1)  /  10  ==  3  *  c  /  2  +  1

@glaforge — @smaldini / #DV13-rtweb

Power asserts

!71

def  (a,  b,  c)  =  [20,  30,  40]  !

assert  a  *  (b  -­‐  1)  /  10  ==  3  *  c  /  2  +  1

Assertion  failed:    !

assert  a  *  (b  -­‐  1)  /  10  ==  3  *  c  /  2  +  1                |  |    |  |        |        |        |  |  |      |                |  580|  29      58      false|  |  60    61                20      30                              |  40                                                            120  !

  at  script1.run(script1.groovy:3)

@glaforge — @smaldini / #DV13-rtweb

Power asserts

!71

def  (a,  b,  c)  =  [20,  30,  40]  !

assert  a  *  (b  -­‐  1)  /  10  ==  3  *  c  /  2  +  1

Assertion  failed:    !

assert  a  *  (b  -­‐  1)  /  10  ==  3  *  c  /  2  +  1                |  |    |  |        |        |        |  |  |      |                |  580|  29      58      false|  |  60    61                20      30                              |  40                                                            120  !

  at  script1.run(script1.groovy:3)Invented by the Spock testing framework

@glaforge — @smaldini / #DV13-rtweb

Null handling

!72

class  Order  {          LineItem  line  }  class  LineItem  {          int  quantity          Item  item  }  class  Item  {          String  name  }  !

def  o  =  new  Order(          line:  new  LineItem(                  quantity:  2,                  item:  null))  !

println  o.line.item.name

@glaforge — @smaldini / #DV13-rtweb

Null handling

!72

class  Order  {          LineItem  line  }  class  LineItem  {          int  quantity          Item  item  }  class  Item  {          String  name  }  !

def  o  =  new  Order(          line:  new  LineItem(                  quantity:  2,                  item:  null))  !

println  o.line.item.name

With Java, you only get an NPE. No idea where it came from!

@glaforge — @smaldini / #DV13-rtweb

Null handling

!72

class  Order  {          LineItem  line  }  class  LineItem  {          int  quantity          Item  item  }  class  Item  {          String  name  }  !

def  o  =  new  Order(          line:  new  LineItem(                  quantity:  2,                  item:  null))  !

println  o.line.item.name

With Java, you only get an NPE. No idea where it came from!

Groovy will say: Cannot get property ‘name’ on null object

@glaforge — @smaldini / #DV13-rtweb

Null handling

!72

class  Order  {          LineItem  line  }  class  LineItem  {          int  quantity          Item  item  }  class  Item  {          String  name  }  !

def  o  =  new  Order(          line:  new  LineItem(                  quantity:  2,                  item:  null))  !

println  o.line.item.name

@glaforge — @smaldini / #DV13-rtweb

Null handling

!72

class  Order  {          LineItem  line  }  class  LineItem  {          int  quantity          Item  item  }  class  Item  {          String  name  }  !

def  o  =  new  Order(          line:  new  LineItem(                  quantity:  2,                  item:  null))  !

println  o.line.item.name

o?.line?.item?.name

@glaforge — @smaldini / #DV13-rtweb

Null handling

!72

class  Order  {          LineItem  line  }  class  LineItem  {          int  quantity          Item  item  }  class  Item  {          String  name  }  !

def  o  =  new  Order(          line:  new  LineItem(                  quantity:  2,                  item:  null))  !

println  o.line.item.name

o?.line?.item?.name

Safe navigation:will just return

null; No NPE

The Truth,the Groovy Truth!

The Truth,the Groovy Truth!

And what if I couldcustomize the truth?

@glaforge — @smaldini / #DV13-rtweb

The Groovy Truth

!74

assert  !(  null  )  assert  !(    ""    )  assert  !(    []    )  assert  !(      0    )

assert  new  Object()  assert  "string"  assert  [1,  2,  3]  assert  1234

@glaforge — @smaldini / #DV13-rtweb

The Groovy Truth

!74

assert  !(  null  )  assert  !(    ""    )  assert  !(    []    )  assert  !(      0    )

assert  new  Object()  assert  "string"  assert  [1,  2,  3]  assert  1234

null, empty, 0-sized, zeroare coerced to false

@glaforge — @smaldini / #DV13-rtweb

The Groovy Truth

!74

assert  !(  null  )  assert  !(    ""    )  assert  !(    []    )  assert  !(      0    )

assert  new  Object()  assert  "string"  assert  [1,  2,  3]  assert  1234

null, empty, 0-sized, zeroare coerced to false

true otherwise

@glaforge — @smaldini / #DV13-rtweb

Customizing the truth!

!75

class  Account  {          String  name          boolean  disabled  =  false  !

       boolean  asBoolean()  {  !disabled  }  }  !

assert    new  Account(name:  'current')  assert  !new  Account(name:  'old',  disabled:  true)

@glaforge — @smaldini / #DV13-rtweb

Customizing the truth!

!75

class  Account  {          String  name          boolean  disabled  =  false  !

       boolean  asBoolean()  {  !disabled  }  }  !

assert    new  Account(name:  'current')  assert  !new  Account(name:  'old',  disabled:  true)

while (account), if (account), etc…

?:

The Elvisoperator!

?:

@glaforge — @smaldini / #DV13-rtweb

Towards Elvis...

!77

@glaforge — @smaldini / #DV13-rtweb

Towards Elvis...

!77

def  (x,  y)  =  ['MacBook  Pro',  'unknown']

@glaforge — @smaldini / #DV13-rtweb

Towards Elvis...

!77

def  (x,  y)  =  ['MacBook  Pro',  'unknown']

@glaforge — @smaldini / #DV13-rtweb

Towards Elvis...

!77

def  (x,  y)  =  ['MacBook  Pro',  'unknown']

if  (x  !=  null  &&  x.size()  >  0)  x  else  y

@glaforge — @smaldini / #DV13-rtweb

Towards Elvis...

!77

def  (x,  y)  =  ['MacBook  Pro',  'unknown']

if  (x  !=  null  &&  x.size()  >  0)  x  else  yif  (x  &&  x.size())  x  else  y

@glaforge — @smaldini / #DV13-rtweb

Towards Elvis...

!77

def  (x,  y)  =  ['MacBook  Pro',  'unknown']

if  (x  !=  null  &&  x.size()  >  0)  x  else  yif  (x  &&  x.size())  x  else  yif  (x)  x  else  y

@glaforge — @smaldini / #DV13-rtweb

Towards Elvis...

!77

def  (x,  y)  =  ['MacBook  Pro',  'unknown']

if  (x  !=  null  &&  x.size()  >  0)  x  else  yif  (x  &&  x.size())  x  else  yif  (x)  x  else  yx  ?  x  :  y

@glaforge — @smaldini / #DV13-rtweb

Towards Elvis...

!77

def  (x,  y)  =  ['MacBook  Pro',  'unknown']

if  (x  !=  null  &&  x.size()  >  0)  x  else  yif  (x  &&  x.size())  x  else  yif  (x)  x  else  yx  ?  x  :  yx  ?:  y

@glaforge — @smaldini / #DV13-rtweb

Towards Elvis...

!77

def  (x,  y)  =  ['MacBook  Pro',  'unknown']

if  (x  !=  null  &&  x.size()  >  0)  x  else  yif  (x  &&  x.size())  x  else  yif  (x)  x  else  yx  ?  x  :  yx  ?:  y

Null, empty, zero-sized... false,

otherwise true!

@glaforge — @smaldini / #DV13-rtweb

Towards Elvis...

!77

def  (x,  y)  =  ['MacBook  Pro',  'unknown']

if  (x  !=  null  &&  x.size()  >  0)  x  else  yif  (x  &&  x.size())  x  else  yif  (x)  x  else  yx  ?  x  :  yx  ?:  y

Null, empty, zero-sized... false,

otherwise true!

Good old ternaryoperator

@glaforge — @smaldini / #DV13-rtweb

Towards Elvis...

!77

def  (x,  y)  =  ['MacBook  Pro',  'unknown']

if  (x  !=  null  &&  x.size()  >  0)  x  else  yif  (x  &&  x.size())  x  else  yif  (x)  x  else  yx  ?  x  :  yx  ?:  y

Null, empty, zero-sized... false,

otherwise true!

Good old ternaryoperatorElvis!

@glaforge — @smaldini / #DV13-rtweb

AST transformations

• Abstract Syntax Tree – in memory representation of your program

before being compiled into bytecode !

• AST transformation == process of transforming the AST of a program before it’s compiled !

• Macro-like compiler hook!

!78

@glaforge — @smaldini / #DV13-rtweb

Lots of AST transformations...

• Code generation

– @ToString, @EqualsAndHashCode, @Canonical, @TupleConstructor, @InheritConstructors, @Category, @IndexedProperty, @Lazy, @Newify

• Class design

– @Delegate, @Immutable, @Memoized, @Singleton, @Mixin

• Logging

– @Log, @Log4j, @Log4j2, @Slf4j

!79

@glaforge — @smaldini / #DV13-rtweb

Lots of AST transformations...

• Safer scripting

– @ConditionalInterrupt, @ThreadInterrupt, @TimedInterupt

• Compiler directives

– @Field, @PackageScope, @AnnotationCollector, @DelegatesTo, @TypeChecked, @CompileStatic, @CompileDynamic

• Swing patterns

– @Bindable, @ListenerList, @Vetoable

!80

@glaforge — @smaldini / #DV13-rtweb

Lots of AST transformations...

• Dependencies handling

– @Grab, @GrabConfig, @GrabExclude, @GrabResolver

• Test assistance

– @NotYetImplemented, @ASTTest

!81

@glaforge — @smaldini / #DV13-rtweb

Immutability

• Implement immutability by the book !

– final class – tuple-style constructor – private final backing fields – defensive copying of collections – equals() and hashCode() methods – toString() method – ...

!82

@glaforge — @smaldini / #DV13-rtweb

Immutability

• Implement immutability by the book !

– final class – tuple-style constructor – private final backing fields – defensive copying of collections – equals() and hashCode() methods – toString() method – ...

!82

Can be error-prone towrite immutableclasses oneself !

@glaforge — @smaldini / #DV13-rtweb

Immutability

• A Person class with – a String name – an int age

!83

public final class Person {! private final String name;! private final int age;!! public Person(String name, int age) {! this.name = name;! this.age = age;! }!! public String getName() {! return name;! }!! public int getAge() {! return age;! }!! public int hashCode() {! return age + 31 * name.hashCode();! }!! public boolean equals(Object other) {! if (other == null) {! return false;! }! if (this == other) {! return true;! }! if (Person.class != other.getClass()) {! return false;! }! Person otherPerson = (Person)other;! if (!name.equals(otherPerson.getName()) {! return false;! }! if (age != otherPerson.getAge()) {! return false;! }! return true;! }!! public String toString() {! return "Person(" + name + ", " + age + ")";! }!}!

@glaforge — @smaldini / #DV13-rtweb

Immutability

• A Person class with – a String name – an int age

!83

public final class Person {! private final String name;! private final int age;!! public Person(String name, int age) {! this.name = name;! this.age = age;! }!! public String getName() {! return name;! }!! public int getAge() {! return age;! }!! public int hashCode() {! return age + 31 * name.hashCode();! }!! public boolean equals(Object other) {! if (other == null) {! return false;! }! if (this == other) {! return true;! }! if (Person.class != other.getClass()) {! return false;! }! Person otherPerson = (Person)other;! if (!name.equals(otherPerson.getName()) {! return false;! }! if (age != otherPerson.getAge()) {! return false;! }! return true;! }!! public String toString() {! return "Person(" + name + ", " + age + ")";! }!}!

Damnverbose

Java!

@glaforge — @smaldini / #DV13-rtweb

Immutability

• A Person class with – a String name – an int age

!83

public final class Person {! private final String name;! private final int age;!! public Person(String name, int age) {! this.name = name;! this.age = age;! }!! public String getName() {! return name;! }!! public int getAge() {! return age;! }!! public int hashCode() {! return age + 31 * name.hashCode();! }!! public boolean equals(Object other) {! if (other == null) {! return false;! }! if (this == other) {! return true;! }! if (Person.class != other.getClass()) {! return false;! }! Person otherPerson = (Person)other;! if (!name.equals(otherPerson.getName()) {! return false;! }! if (age != otherPerson.getAge()) {! return false;! }! return true;! }!! public String toString() {! return "Person(" + name + ", " + age + ")";! }!}!

Damnverbose

Java!

Although it’s also avalid Groovy

program!

@glaforge — @smaldini / #DV13-rtweb

@Immutable

!84

import  groovy.transform.*  !

@Immutable  class  Person  {          String  name          int  age  }

@glaforge — @smaldini / #DV13-rtweb

Memoization

• Cache the result of previous invocations of closures or methods with the same set of argument values

!85

import  groovy.transform.*  !

@Memoized  long  fib(long  n)  {          if  (n  ==  0)  0          else  if  (n  ==  1)  1          else  fib(n  -­‐  1)  +  fib(n  -­‐  2)  }  !

println  fib(40)

@glaforge — @smaldini / #DV13-rtweb

Memoization

• Cache the result of previous invocations of closures or methods with the same set of argument values

!85

import  groovy.transform.*  !

@Memoized  long  fib(long  n)  {          if  (n  ==  0)  0          else  if  (n  ==  1)  1          else  fib(n  -­‐  1)  +  fib(n  -­‐  2)  }  !

println  fib(40)

Best applied toside-effect free

functions

Groovy allows youto be lazy

Groovy allows youto be lazy

The compiler will dothe job for you

Groovy allows youto be lazy

The compiler will dothe job for you

More concise, morereadable code

Groovy allows youto be lazy

The compiler will dothe job for you

More concise, morereadable code

Less stuff to maintainand worry about

@glaforge — @smaldini / #DV13-rtweb

@TypeChecked & @CompileStatic

• Static type checking with @TypeChecked, throws compilation errors on... – typos in method and variable names – incompatible return types – wrong type assignments !

• Supports fine-grained type inference – « Least Upper Bound » – « Flow typing »

!87

@glaforge — @smaldini / #DV13-rtweb

@TypeChecked & @CompileStatic

• Static type checking with @TypeChecked, throws compilation errors on... – typos in method and variable names – incompatible return types – wrong type assignments !

• Supports fine-grained type inference – « Least Upper Bound » – « Flow typing »

!87

You can even extend thestatic type checker!

@glaforge — @smaldini / #DV13-rtweb

@TypeChecked & @CompileStatic

• Static type checking with @TypeChecked, throws compilation errors on... – typos in method and variable names – incompatible return types – wrong type assignments !

• Supports fine-grained type inference – « Least Upper Bound » – « Flow typing »

!87

You can even extend thestatic type checker!

Type check DSLs ordynamic features!

@glaforge — @smaldini / #DV13-rtweb

@TypeChecked & @CompileStatic

!

• What is type checked can also be compiled statically with @CompileStatic

!

– generate the same bytecode as javac !

– same performance as Java

!88

@glaforge — @smaldini / #DV13-rtweb

Static compilation performance

!89

Fibonacci Pi (π) quadrature

Binarytrees

Java 191 ms 97 ms 3.6 s

Staticcompilation 197 ms 101 ms 4.3 s

Primitive optimizations 360 ms 111 ms 23.7 s

No prim.optimizations 2590 ms 3220 ms 50.0 s1.

71.

82.

x

Reactor

Part 2

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Housekeeping

!

• Tweet questions during the presentation – @ProjectReactor

!

• Stop us anywhere if you have a question – There are no stupid questions, only stupid answers!

!91

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Reactive Architecture ?

!92

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Reactive Architecture ?

!92

REACTIVE

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Reactive Architecture ?

!92

EVENT DRIVEN

REACTIVE

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Reactive Architecture ?

!92

EVENT DRIVENAGILE

REACTIVE

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Reactive Architecture ?

!92

EVENT DRIVENAGILE

REACTIVE

SCALABLE

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Reactive Architecture ?

!92

EVENT DRIVENAGILE

REACTIVE

SCALABLE

AVAILABLE

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Reactive Architecture ?

!92

EVENT DRIVENAGILE

REACTIVE

SCALABLE

AVAILABLE

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Reactive Architecture ?

!93

• A good description is available on: http://www.reactivemanifesto.org/ !

• Functional Programming helps as it is stimulus based by nature !

• Groovy is a perfect candidate: Closures and DSL are first class citizen !

• Reactor completes the picture by providing abstractions

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Threading model matters

!94

• Context switching hurts performances

• Locking hurts performances

• Message passing hurts performances

• Blocking for a thread hurts performances

• Creating Threads needs memory

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Dealing with performances

!95

• Actors solve the locking and context switching issues by becoming state boxes • One thread assigned per consumer • One thread will ever access a property

• Non Blocking Programming solves thread creation and waiting issues by delaying logic • Callback will be executed when possible (Lazy) • Reallocate the blocking time to process something else

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — LMAX Disruptor and Ring Buffer

!96

• LMAX Disruptor deals with message passing issues • Based on a Ring Buffer structure • “Mechanical Sympathy” in Disruptor

!

• http://lmax-exchange.github.com/disruptor/files/Disruptor-1.0.pdf

• http://mechanitis.blogspot.co.uk/2011/06/dissecting-disruptor-whats-so-special.html

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Disruptor performances

!97

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Trisha’s pretty picture of Disruptor

!98

http://mechanitis.blogspot.co.uk/2011/07/dissecting-disruptor-writing-to-ring.html

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — In love with Disruptor

!99

• Reactor best performances are derived from LMAX Disruptor !

• LMAX Disruptor can be considered as an evolution of the Actor Model: !

• Still avoid locking and deals with context switching • Producer/Consumer decoupled • Add Pipelining, Batching and more

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — A foundation part of Spring IO

!100

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — What is it?

!

• Reactor is a distillation of other libraries and best-practices – Elements of other patterns and libraries

surface throughout Reactor's abstractions !

• http://stackoverflow.com/questions/16595393/akka-or-reactor

!101

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — What can I build with it?

• Reactor applications are reactive – Reactive Extensions in .NET – Netflix RxJava – Observer pattern

!

• Reactor applications route events based on a Selector – Like a routing topic, but can be any object – Regex, URI template, Class.isAssingableFrom, custom logic

!102

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Landscape

!103

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — What does it look like?

!104

def  env  =  new  Environment()  !def  reactor  =  Reactors.reactor().env(env).dispatcher(RING_BUFFER).get()  !reactor.on($('topic')){  Event<String>  ev  -­‐>                  println  "Hello  $ev.data"  }  !reactor.notify('topic',  Event.wrap('John  Doe'))

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — What does it look like?

!104

def  env  =  new  Environment()  !def  reactor  =  Reactors.reactor().env(env).dispatcher(RING_BUFFER).get()  !reactor.on($('topic')){  Event<String>  ev  -­‐>                  println  "Hello  $ev.data"  }  !reactor.notify('topic',  Event.wrap('John  Doe'))

Create a reactor context

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — What does it look like?

!104

def  env  =  new  Environment()  !def  reactor  =  Reactors.reactor().env(env).dispatcher(RING_BUFFER).get()  !reactor.on($('topic')){  Event<String>  ev  -­‐>                  println  "Hello  $ev.data"  }  !reactor.notify('topic',  Event.wrap('John  Doe'))

Create a reactor contextBuild a reactor

parameter

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — What does it look like?

!104

def  env  =  new  Environment()  !def  reactor  =  Reactors.reactor().env(env).dispatcher(RING_BUFFER).get()  !reactor.on($('topic')){  Event<String>  ev  -­‐>                  println  "Hello  $ev.data"  }  !reactor.notify('topic',  Event.wrap('John  Doe'))

Create a reactor context

Trigger reactor ‘topic’ key

Build a reactorparameter

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — What does it look like?

!104

def  env  =  new  Environment()  !def  reactor  =  Reactors.reactor().env(env).dispatcher(RING_BUFFER).get()  !reactor.on($('topic')){  Event<String>  ev  -­‐>                  println  "Hello  $ev.data"  }  !reactor.notify('topic',  Event.wrap('John  Doe'))

Create a reactor context

Trigger reactor ‘topic’ key

React on ‘topic’ events

Build a reactorparameter

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Selectors

• Selectors are the left-hand side of an equality comparison !

– A Selector can be created from any object using $(obj) (or the long form: Selectors.object(obj)) !

– A Selector can extract data from the matched key !

– Predicate<T> Selectors can be created to match on domain-specific criteria like header values

!105

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — RegexSelector

• A RegexSelector will match a String by executing the regex over it – R(“some.(.*)”) – Selectors.regex(“some.(.*)”)

!106

@glaforge — @smaldini / #DV13-rtweb

Reactor — RegexSelector

!

• A RegexSelector will match a String by executing the regex over it !

– R(“some.(.*)”) !

– Selectors.regex(“some.(.*)”)

!107

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — RegexSelector

!108

reactor.on(R('some.(.+)')){  Event<String>  ev  -­‐>                  def  s  =  ev.headers.get('group1')  }                                !

reactor.notify('some.topic',  Event.wrap('John  Doe'))

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — RegexSelector

!108

reactor.on(R('some.(.+)')){  Event<String>  ev  -­‐>                  def  s  =  ev.headers.get('group1')  }                                !

reactor.notify('some.topic',  Event.wrap('John  Doe'))

Use a Regex Selector and capture group

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — RegexSelector

!108

reactor.on(R('some.(.+)')){  Event<String>  ev  -­‐>                  def  s  =  ev.headers.get('group1')  }                                !

reactor.notify('some.topic',  Event.wrap('John  Doe'))

Use a Regex Selector and capture group

Notify a simple Stringkey to be matched

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — RegexSelector

!108

reactor.on(R('some.(.+)')){  Event<String>  ev  -­‐>                  def  s  =  ev.headers.get('group1')  }                                !

reactor.notify('some.topic',  Event.wrap('John  Doe'))

Use a Regex Selector and capture group

Notify a simple Stringkey to be matched

s will be ‘topic’

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — UriSelector

• A UriTemplateSelector will match a String by extracting bits from a URI !

– U(“/some/{path}”) – Selectors.uri(“/some/{path}”)

!109

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — UriSelector

!110

reactor.on(U('/some/**/{topic}')){  Event<String>  ev  -­‐>                  def  s  =  ev.headers['topic']  }                !

reactor.notify('/some/to/topic',  Event.wrap('John  Doe'))

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — UriSelector

!110

reactor.on(U('/some/**/{topic}')){  Event<String>  ev  -­‐>                  def  s  =  ev.headers['topic']  }                !

reactor.notify('/some/to/topic',  Event.wrap('John  Doe'))

Use a URI Selector and capture fragment

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — UriSelector

!110

reactor.on(U('/some/**/{topic}')){  Event<String>  ev  -­‐>                  def  s  =  ev.headers['topic']  }                !

reactor.notify('/some/to/topic',  Event.wrap('John  Doe'))

Use a URI Selector and capture fragment

Notify a simple StringURI key to be matched

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — UriSelector

!110

reactor.on(U('/some/**/{topic}')){  Event<String>  ev  -­‐>                  def  s  =  ev.headers['topic']  }                !

reactor.notify('/some/to/topic',  Event.wrap('John  Doe'))

Use a URI Selector and capture fragment

Notify a simple StringURI key to be matched

s will be ‘topic’

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Consumer, Function, Supplier, Predicate

!111

public  interface  Consumer<T>  {                  void  accept(T  t);    }                !

public  interface  Supplier<T>  {                    T  get();      }  !

public  interface  Function<T,  V>  {                  V  apply(T  t);  }                !

public  abstract  class  Predicate<T>  {                    boolean  test(T  t);    }      

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Consumer, Function, Supplier, Predicate

!111

public  interface  Consumer<T>  {                  void  accept(T  t);    }                !

public  interface  Supplier<T>  {                    T  get();      }  !

public  interface  Function<T,  V>  {                  V  apply(T  t);  }                !

public  abstract  class  Predicate<T>  {                    boolean  test(T  t);    }      

Generic Callback

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Consumer, Function, Supplier, Predicate

!111

public  interface  Consumer<T>  {                  void  accept(T  t);    }                !

public  interface  Supplier<T>  {                    T  get();      }  !

public  interface  Function<T,  V>  {                  V  apply(T  t);  }                !

public  abstract  class  Predicate<T>  {                    boolean  test(T  t);    }      

Generic Callback

Object Factory

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Consumer, Function, Supplier, Predicate

!111

public  interface  Consumer<T>  {                  void  accept(T  t);    }                !

public  interface  Supplier<T>  {                    T  get();      }  !

public  interface  Function<T,  V>  {                  V  apply(T  t);  }                !

public  abstract  class  Predicate<T>  {                    boolean  test(T  t);    }      

Generic Callback

Object Factory

Map Operation

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Consumer, Function, Supplier, Predicate

!111

public  interface  Consumer<T>  {                  void  accept(T  t);    }                !

public  interface  Supplier<T>  {                    T  get();      }  !

public  interface  Function<T,  V>  {                  V  apply(T  t);  }                !

public  abstract  class  Predicate<T>  {                    boolean  test(T  t);    }      

Generic Callback

Object Factory

Map Operation

Filter Operation

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Streams

● Streams allow for composition of functions on data − Callback++ − Netflix RxJava Observable, JDK 8 Stream

!112

Stream<String>  str  =  obtainStream()  !

str.map{  it.toUpperCase()  }                  .filter{  someCondition()  }                  .consume{  s  -­‐>  log.info  "consumed  string  $s"  }

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Streams

● Streams allow for composition of functions on data − Callback++ − Netflix RxJava Observable, JDK 8 Stream

!112

Stream<String>  str  =  obtainStream()  !

str.map{  it.toUpperCase()  }                  .filter{  someCondition()  }                  .consume{  s  -­‐>  log.info  "consumed  string  $s"  }

Coerces to Function

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Streams

● Streams allow for composition of functions on data − Callback++ − Netflix RxJava Observable, JDK 8 Stream

!112

Stream<String>  str  =  obtainStream()  !

str.map{  it.toUpperCase()  }                  .filter{  someCondition()  }                  .consume{  s  -­‐>  log.info  "consumed  string  $s"  }

Coerces to Predicate

Coerces to Function

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Streams

● Streams allow for composition of functions on data − Callback++ − Netflix RxJava Observable, JDK 8 Stream

!112

Stream<String>  str  =  obtainStream()  !

str.map{  it.toUpperCase()  }                  .filter{  someCondition()  }                  .consume{  s  -­‐>  log.info  "consumed  string  $s"  }

Coerces to Predicate

Coerces to Function

Coerces to Consumer

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Promises

● Promises supersedes Java Future for composition − Share common functions with Stream − Stateful: only 1 transition allowed

!113

Promise<String>  p  =  doLater()  !

String  s  =  p    .onSuccess  {  s  -­‐>  log.info  "consumed  string  $s"  }      .onFailure  {  t  -­‐>  log.error  "$t.message"  }        .onComplete  {  log.info  'complete'  }          .await(5,  SECONDS)  !

p.map{  it.toUpperCase()  }.consume{s  -­‐>  log.info  "UC:  $s"}  

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Promises

● Promises supersedes Java Future for composition − Share common functions with Stream − Stateful: only 1 transition allowed

!113

Promise<String>  p  =  doLater()  !

String  s  =  p    .onSuccess  {  s  -­‐>  log.info  "consumed  string  $s"  }      .onFailure  {  t  -­‐>  log.error  "$t.message"  }        .onComplete  {  log.info  'complete'  }          .await(5,  SECONDS)  !

p.map{  it.toUpperCase()  }.consume{s  -­‐>  log.info  "UC:  $s"}  Block for a return value

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Processor● Thin wrapper around Disruptor RingBuffer − Converts Disruptor API to Reactor API − Uber fast performance for #UberFastData

!114

Processor<Buffer>  proc  !

Operation<Buffer>  op  =  proc.prepare()  op.get().append(data).flip()  op.commit()  !

proc.batch(512)  {  it.append(data).flip()  }

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Processor● Thin wrapper around Disruptor RingBuffer − Converts Disruptor API to Reactor API − Uber fast performance for #UberFastData

!114

Processor<Buffer>  proc  !

Operation<Buffer>  op  =  proc.prepare()  op.get().append(data).flip()  op.commit()  !

proc.batch(512)  {  it.append(data).flip()  }

Fill 512 slots and release once

@glaforge — @smaldini / #DV13-rtweb

Reactor — Spring

!

● Helpers to integrate Reactor into ApplicationContext !

− @EnableReactor for easy configuration !

− Wiring annotated handlers

!115

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Spring

!116

@Configuration  @EnableReactor  class  ReactorConfiguration  {  !

               @Bean  Reactor  input(Environment  env)  {                                  Reactors.reactor().env(env)                                                  .dispatcher(RING_BUFFER).get()                  }                !

               @Bean  Reactor  output(Environment  env)  {                                  Reactors.reactor().env(env)                                                  .dispatcher(RING_BUFFER).get()                  }                }

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Spring

!116

@Configuration  @EnableReactor  class  ReactorConfiguration  {  !

               @Bean  Reactor  input(Environment  env)  {                                  Reactors.reactor().env(env)                                                  .dispatcher(RING_BUFFER).get()                  }                !

               @Bean  Reactor  output(Environment  env)  {                                  Reactors.reactor().env(env)                                                  .dispatcher(RING_BUFFER).get()                  }                }

Setup Environment

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Spring

!117

@Component  class  SimpleHandler  {  !

   @Autowired  Reactor  reactor  !

   @Selector('test.topic')  void  onTestTopic(String  s)  {                      //  Handle  data      }  }

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Spring

!117

@Component  class  SimpleHandler  {  !

   @Autowired  Reactor  reactor  !

   @Selector('test.topic')  void  onTestTopic(String  s)  {                      //  Handle  data      }  }

Inject reactor

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Spring

!117

@Component  class  SimpleHandler  {  !

   @Autowired  Reactor  reactor  !

   @Selector('test.topic')  void  onTestTopic(String  s)  {                      //  Handle  data      }  }

Inject reactor

Register consumer on @reactor

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Spring

● DispatcherTaskExecutor − Not really a high-scale TaskExecutor − Used to get Spring components running in same thread as

Reactor Consumers ● ConversionService integration ● PromiseHandlerMethodReturnValueHandler (!) ● ReactorSubscribableChannel

!118

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy

● First class citizen language implementation − @CompileStatic ready − Prominent use of Closure as Consumers, Functions and

more − Operator overloading for elegant programming − Full Reactor system Builder

!119

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy

!120

@CompileStatic  def  welcome()  {                  reactor.on('greetings')  {  String  s  -­‐>                                  reply  "hello  $s"                                  reply  "how  are  you?"                  }  !                reactor.notify  'greetings',  'Jon'  !                reactor.send('greetings',  'Stephane')  {                                  println  it                                  cancel()                  }  }

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy

!120

@CompileStatic  def  welcome()  {                  reactor.on('greetings')  {  String  s  -­‐>                                  reply  "hello  $s"                                  reply  "how  are  you?"                  }  !                reactor.notify  'greetings',  'Jon'  !                reactor.send('greetings',  'Stephane')  {                                  println  it                                  cancel()                  }  }

Works with Groovy 2 CompileStatic

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy

!120

@CompileStatic  def  welcome()  {                  reactor.on('greetings')  {  String  s  -­‐>                                  reply  "hello  $s"                                  reply  "how  are  you?"                  }  !                reactor.notify  'greetings',  'Jon'  !                reactor.send('greetings',  'Stephane')  {                                  println  it                                  cancel()                  }  }

Works with Groovy 2 CompileStaticCoerce String to $(string)

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy

!120

@CompileStatic  def  welcome()  {                  reactor.on('greetings')  {  String  s  -­‐>                                  reply  "hello  $s"                                  reply  "how  are  you?"                  }  !                reactor.notify  'greetings',  'Jon'  !                reactor.send('greetings',  'Stephane')  {                                  println  it                                  cancel()                  }  }

Works with Groovy 2 CompileStaticCoerce String to $(string)

Send data back usingreplyTo key header

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy

!120

@CompileStatic  def  welcome()  {                  reactor.on('greetings')  {  String  s  -­‐>                                  reply  "hello  $s"                                  reply  "how  are  you?"                  }  !                reactor.notify  'greetings',  'Jon'  !                reactor.send('greetings',  'Stephane')  {                                  println  it                                  cancel()                  }  }

Works with Groovy 2 CompileStaticCoerce String to $(string)

Send data back usingreplyTo key header

Coerce data arg toEvent.wrap(data)

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy

!120

@CompileStatic  def  welcome()  {                  reactor.on('greetings')  {  String  s  -­‐>                                  reply  "hello  $s"                                  reply  "how  are  you?"                  }  !                reactor.notify  'greetings',  'Jon'  !                reactor.send('greetings',  'Stephane')  {                                  println  it                                  cancel()                  }  }

Works with Groovy 2 CompileStaticCoerce String to $(string)

Send data back usingreplyTo key header

Coerce data arg toEvent.wrap(data)

Notify & Listen for replies

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy

!120

@CompileStatic  def  welcome()  {                  reactor.on('greetings')  {  String  s  -­‐>                                  reply  "hello  $s"                                  reply  "how  are  you?"                  }  !                reactor.notify  'greetings',  'Jon'  !                reactor.send('greetings',  'Stephane')  {                                  println  it                                  cancel()                  }  }

Works with Groovy 2 CompileStaticCoerce String to $(string)

Send data back usingreplyTo key header

Coerce data arg toEvent.wrap(data)

Notify & Listen for repliesStop listening for replies

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy Promises and Streams

!121

def  promise  =  Promises.task  {  longStuff();  1  }  get()                                                          !

def  transformation  =  promise  |  {  it  +  1  }  transformation.await()  ==  2            !

def  deferredStream  =  Streams.defer().get()              (deferredStream  &  {  it  >  2  })  <<  {  send(it)  }  !

deferredStream  <<  1  <<  2  <<  3  <<  4

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy Promises and Streams

!121

def  promise  =  Promises.task  {  longStuff();  1  }  get()                                                          !

def  transformation  =  promise  |  {  it  +  1  }  transformation.await()  ==  2            !

def  deferredStream  =  Streams.defer().get()              (deferredStream  &  {  it  >  2  })  <<  {  send(it)  }  !

deferredStream  <<  1  <<  2  <<  3  <<  4

Build an async function

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy Promises and Streams

!121

def  promise  =  Promises.task  {  longStuff();  1  }  get()                                                          !

def  transformation  =  promise  |  {  it  +  1  }  transformation.await()  ==  2            !

def  deferredStream  =  Streams.defer().get()              (deferredStream  &  {  it  >  2  })  <<  {  send(it)  }  !

deferredStream  <<  1  <<  2  <<  3  <<  4

Build an async function

Pipe promise with theclosure transformation

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy Promises and Streams

!121

def  promise  =  Promises.task  {  longStuff();  1  }  get()                                                          !

def  transformation  =  promise  |  {  it  +  1  }  transformation.await()  ==  2            !

def  deferredStream  =  Streams.defer().get()              (deferredStream  &  {  it  >  2  })  <<  {  send(it)  }  !

deferredStream  <<  1  <<  2  <<  3  <<  4

Build an async function

Pipe promise with theclosure transformation

Filter the stream withthe right hand closure

predicate

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy Promises and Streams

!121

def  promise  =  Promises.task  {  longStuff();  1  }  get()                                                          !

def  transformation  =  promise  |  {  it  +  1  }  transformation.await()  ==  2            !

def  deferredStream  =  Streams.defer().get()              (deferredStream  &  {  it  >  2  })  <<  {  send(it)  }  !

deferredStream  <<  1  <<  2  <<  3  <<  4

Build an async function

Pipe promise with theclosure transformation

Filter the stream withthe right hand closure

predicate

Add callback after filter

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy Promises and Streams

!121

def  promise  =  Promises.task  {  longStuff();  1  }  get()                                                          !

def  transformation  =  promise  |  {  it  +  1  }  transformation.await()  ==  2            !

def  deferredStream  =  Streams.defer().get()              (deferredStream  &  {  it  >  2  })  <<  {  send(it)  }  !

deferredStream  <<  1  <<  2  <<  3  <<  4

Build an async function

Pipe promise with theclosure transformation

Filter the stream withthe right hand closure

predicate

Add callback after filter

Send data

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy Environment

!122

GroovyEnvironment.create  {                  environment  {                                  defaultDispatcher  =  "test"  !

                               dispatcher('test')  {                                                  type  =  DispatcherType.SYNCHRONOUS                                  }                  }                }.environment()

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy Environment

!122

GroovyEnvironment.create  {                  environment  {                                  defaultDispatcher  =  "test"  !

                               dispatcher('test')  {                                                  type  =  DispatcherType.SYNCHRONOUS                                  }                  }                }.environment()

Works with Groovy 2@CompileStatic

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy Environment

!122

GroovyEnvironment.create  {                  environment  {                                  defaultDispatcher  =  "test"  !

                               dispatcher('test')  {                                                  type  =  DispatcherType.SYNCHRONOUS                                  }                  }                }.environment()

Works with Groovy 2@CompileStatic

Prepare an environmentbuilder

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy Environment

!122

GroovyEnvironment.create  {                  environment  {                                  defaultDispatcher  =  "test"  !

                               dispatcher('test')  {                                                  type  =  DispatcherType.SYNCHRONOUS                                  }                  }                }.environment()

Works with Groovy 2@CompileStatic

Prepare an environmentbuilder

which dispatcher to usewhen creating a reactor

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy Environment

!122

GroovyEnvironment.create  {                  environment  {                                  defaultDispatcher  =  "test"  !

                               dispatcher('test')  {                                                  type  =  DispatcherType.SYNCHRONOUS                                  }                  }                }.environment()

Works with Groovy 2@CompileStatic

Prepare an environmentbuilder

which dispatcher to usewhen creating a reactor

Build a standardDispatcher

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy Environment

!122

GroovyEnvironment.create  {                  environment  {                                  defaultDispatcher  =  "test"  !

                               dispatcher('test')  {                                                  type  =  DispatcherType.SYNCHRONOUS                                  }                  }                }.environment()

Works with Groovy 2@CompileStatic

Prepare an environmentbuilder

which dispatcher to usewhen creating a reactor

Build a standardDispatcherReturn a ready to

use Environment

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy Environment

!123

GroovyEnvironment.create  {                  reactor('test1')  {                                  stream('test')  {                                                                                  consume  {  ev-­‐>                                                                                                                                                        log.info  ev.data                                                                                                                                  }                                                                              }                                                                                                                                              on('test')  {                                                  reply  it                                  }                  }                                                                                                            }  

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy Environment

!123

GroovyEnvironment.create  {                  reactor('test1')  {                                  stream('test')  {                                                                                  consume  {  ev-­‐>                                                                                                                                                        log.info  ev.data                                                                                                                                  }                                                                              }                                                                                                                                              on('test')  {                                                  reply  it                                  }                  }                                                                                                            }  

Build a named Reactor

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy Environment

!123

GroovyEnvironment.create  {                  reactor('test1')  {                                  stream('test')  {                                                                                  consume  {  ev-­‐>                                                                                                                                                        log.info  ev.data                                                                                                                                  }                                                                              }                                                                                                                                              on('test')  {                                                  reply  it                                  }                  }                                                                                                            }  

Build a named Reactor

Intercept the datastream coming by the

selector $(‘test’)

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy Environment

!123

GroovyEnvironment.create  {                  reactor('test1')  {                                  stream('test')  {                                                                                  consume  {  ev-­‐>                                                                                                                                                        log.info  ev.data                                                                                                                                  }                                                                              }                                                                                                                                              on('test')  {                                                  reply  it                                  }                  }                                                                                                            }  

Build a named Reactor

Intercept the datastream coming by the

selector $(‘test’)

Stream builder

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Groovy Environment

!123

GroovyEnvironment.create  {                  reactor('test1')  {                                  stream('test')  {                                                                                  consume  {  ev-­‐>                                                                                                                                                        log.info  ev.data                                                                                                                                  }                                                                              }                                                                                                                                              on('test')  {                                                  reply  it                                  }                  }                                                                                                            }  

Build a named Reactor

Intercept the datastream coming by the

selector $(‘test’)

Stream builder

Attach inline consumers

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Extensive Awesomeness

● TCP Client/Server, with a Netty 4 implementation

● Buffer tools

● Sequencer support, for event ordering

● Work Queue support with OoB Java Chronicle implementation

● Log Appender

!124

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Roadmap

● 1.1 discussions − StateBox: a safe tool for concurrent writes

− Better Timer management

− Spring XD, Spring 4

− Exploring Distributed Reactors

● Voice your interest and your use-case here

!125

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Roadmap

● 1.1 discussions − HTTP helpers

− Improved RingBuffer API for multithreaded consumers

(slow consumers)

− More Groovy Love: Buffer, TCP, Processor, Time,

!126

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Uber Community Contribs

● Meltdown: A Clojure binding by @michaelklishin & @ifesdjeen

− https://github.com/clojurewerkz/meltdown

!127

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Reactor — Uber Community Contribs

!

● High Performance Couchbase ingestion by @daschl − http://nitschinger.at/Using-the-Reactor-Processor-for-High-

Performance-TCP !

● Benefits of using Reactor Processor, TCP and Batching facilities

!128

Demo

Grails

Part 3

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — The Age of Asynchronous

Fact #1: – HTTP request thread is critical path – Do the barely necessary for fast rendering – If it’s long*, do it in a separate thread

!131

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — The Age of Asynchronous

Fact #2: – Creating new Threads needs caution – Context switching hurts performances – Concurrent programming is tricky*

!132

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — The Age of Asynchronous

Fact #3: – Burden on your application is never constant – Scaling Up is a good start… – …And Scaling Out is only next

!133

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — The Age of Asynchronous

So I have to use background threads ? But using them might lead to issues and headaches ? And what if I really need to scale out ?

!134

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Reactive Programming Recap

• Adding a level of indirection : Driving your application with Events

• Laziness is key • Scale up/out by tweaking dispatching

!135

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — The Big Picture

!136

Events Bus

Service Consumer

Service Consumer

Service Consumer

Service Consumer

Service Consumer

Application

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — The Big Picture

!136

Events Bus

Publish/Subscribe

Service Consumer

Service Consumer

Service Consumer

Service Consumer

Service Consumer

Application

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — The Big Picture

!136

Events Bus

Publish/Subscribe

Service Consumer

Service Consumer

Service Consumer

Service Consumer

Service Consumer

Application

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — The Big Picture

!136

Events Bus

Publish/Subscribe

Service Consumer

Service Consumer

Service Consumer

Service Consumer

Service Consumer

Application

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — The Big Picture

!137

Events Bus

Publish/Subscribe

App

App

App

App

App

Cloud

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — The Big Picture

!137

Events Bus

Publish/Subscribe

App

App

App

App

App

Cloud

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb !138

Introducing  GRAILS-­‐EVENTS  plugin

BOOM!

worse slide ever™

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Origins : Platform-Core plugin

• An initiative to provide modern tools for grails development

!

• Among them: the Events API

• An abstraction used in a few Grails applications today to decouple logic from producers

!

• grails-events can be considered as Platform Core Events API 2.0

!139

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Why a new plugin ?

• New features. And quite a few. – Streaming data, Selectors, Queue

!

• Based on a new solid foundation – Reactor – Where Platform-core Events best ideas have leaked

!

• Semantic changes – But relatively straightforward migration path

!140

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Why a new plugin ?

• Lightweight, only focused on events

!

• Ready to be embedded in a future Grails version

• Complete the asynchronous story

• Could enable runtime plugin deployment

!141

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — So what Grails Events is about

• Grails Apps and Plugins can use Events to:

– Listen for plugins/app events

– Start simple with in-memory eventing (#uberfastdata)

– Do Asynchronous calls (default)

– Increase in flexibility if required

!142

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Installing Grails Events

• It’s a binary plugin (!) • Requires Grails 2.2+

!143

repositories  {          //...                mavenRepo  "http://repo.springsource.org/libs-­‐snapshot"          mavenRepo  "http://repo.grails.org/grails/libs-­‐snapshots-­‐local/"  }  !

dependencies  {          compile  'org.grails.plugins:events:1.0.0.BUILD-­‐SNAPSHOT'  }  

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Semantics: Consumer

• A Consumer:

– Accepts an Event

– Is registered in a Service or Events artifact, or by calling on()

– Can be thread safe

• Depending on the dispatcher type

• Assuming the consumer is not registered more than once

!144

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Semantics: Selector

• A Selector:

– Matches an event key

– Is paired with a consumer during its registration

– Any bean method can be transformed into consumer with @Selector

!145

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Semantics: Reactor

• A Reactor: – Is a dedicated Consumer Registry – Has an assigned Dispatcher – Uses a specific Event Router

!

• Usually, if the Dispatcher doesn’t need to be adapted, reuse the default reactor grailsReactor

!146

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Pattern Examples : Event Driven CQRS

!147

GORM

http://martinfowler.com/bliki/CQRS.html

DBPostProcessingService

SaveService

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Pattern Examples : Event Driven CQRS

!147

GORM

http://martinfowler.com/bliki/CQRS.html

DBPostProcessingService

SaveService

Consume afterInsert events

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Pattern Examples : Event Driven CQRS

!147

GORM

http://martinfowler.com/bliki/CQRS.html

DBPostProcessingService

SaveService

Consume afterInsert events

Save a GORM entity

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Pattern Examples : Event Driven CQRS

!147

GORM

http://martinfowler.com/bliki/CQRS.html

DBPostProcessingService

SaveService

Consume afterInsert events

Save a GORM entity

Insert Record

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Pattern Examples : Event Driven CQRS

!147

GORM

http://martinfowler.com/bliki/CQRS.html

DBPostProcessingService

SaveService

Consume afterInsert events

Save a GORM entity

Insert RecordTrigger afterInsert

event

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Pattern Examples : Modular Architecture

!148

Notification Plugin

RequestServiceMain Application

NotificationService

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Pattern Examples : Modular Architecture

!148

Notification Plugin

RequestServiceMain Application

NotificationService

Trigger ‘request’ event

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Pattern Examples : Modular Architecture

!148

Notification Plugin

RequestServiceMain Application

NotificationService

Trigger ‘request’ event

Create a decoupled module

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Pattern Examples : Modular Architecture

!148

Notification Plugin

RequestServiceMain Application

NotificationService

Trigger ‘request’ event

Create a decoupled module Consume ‘request’ events

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Pattern Examples : Modular Architecture

!148

Notification Plugin

RequestServiceMain Application

NotificationService

Trigger ‘request’ event

Create a decoupled module Consume ‘request’ events

Core application untouched

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Pattern Examples : Background Processing

!149

REST service

Grails Controller HTTP Request

Request Service

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Pattern Examples : Background Processing

!149

REST service

Grails Controller HTTP Request

Return immediately

Request Service

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Pattern Examples : Background Processing

!149

REST service

Grails Controller HTTP Request

Return immediately

Request Service

Trigger ‘request’event (async)

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Pattern Examples : Background Processing

!149

REST service

Grails Controller HTTP Request

Return immediately

Request Service

Trigger ‘request’event (async)

Long REST call

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Sending Events (grails-platform-core)

!150

def  user  =  new  User(params).save()  !

event('mailRegistration',  user)      //event('mailRegistration',  user).waitFor()      //event  topic:'mailRegistration',  data:user      //event  topic:'mailRegistration',  data:user,  fork:false      render(view:'sendingRegistrationMail')

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Sending Events (grails-events)

!151

def  user  =  new  User(params).save()  !

event('mailRegistration',  user)                event('mailRegistration',  user)  {          if(it  ==  ‘end')  {                  cancel()          }  }      !

//  event  key:  'mailRegistration',  data:  user  !

render(view:  'sendingRegistrationMail')

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Sending Events (grails-events)

!151

def  user  =  new  User(params).save()  !

event('mailRegistration',  user)                event('mailRegistration',  user)  {          if(it  ==  ‘end')  {                  cancel()          }  }      !

//  event  key:  'mailRegistration',  data:  user  !

render(view:  'sendingRegistrationMail')

Non blocking call totrigger app consumers

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Sending Events (grails-events)

!151

def  user  =  new  User(params).save()  !

event('mailRegistration',  user)                event('mailRegistration',  user)  {          if(it  ==  ‘end')  {                  cancel()          }  }      !

//  event  key:  'mailRegistration',  data:  user  !

render(view:  'sendingRegistrationMail')

Non blocking call totrigger app consumers

Do things on each reply

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Sending Events (grails-events)

!151

def  user  =  new  User(params).save()  !

event('mailRegistration',  user)                event('mailRegistration',  user)  {          if(it  ==  ‘end')  {                  cancel()          }  }      !

//  event  key:  'mailRegistration',  data:  user  !

render(view:  'sendingRegistrationMail')

Non blocking call totrigger app consumers

Do things on each reply

Map notation

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Consuming Events (grails-platform-core)

!152

class  UserService{          @grails.events.Listener          def  mailRegistration(User  user)  {                  sendMail  {                          to  user.mail                          subject  "Confirmation"                          html  g.render(template:"userMailConfirmation")                  }          }  !        @grails.events.Listener(topic=  "mailRegistration")          def  mailRegistration2(org.grails.plugin.platform.events.EventMessage  msg)  {                  sendMail{                        to  msg.data.mail                        subject  "Confirmation"                        html  g.render(template:  "userMailConfirmation")                  }          }  }

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

class  UserService{              @reactor.spring.annotation.Selector          def  mailRegistration(User  user){                  sendMail{                          to  user.mail                          subject  "Confirmation"                          html  g.render(template:  "userMailConfirmation")                  }          }  !        @reactor.spring.annotation.Selector("mailRegistration")          def  mailRegistration2(reactor.event.Event  msg){                  sendMail{                          to  msg.data.mail                          subject  "Confirmation"                          html  g.render(template:"userMailConfirmation")                  }          }  }

Grails — Consuming Events (grails-events)

!153

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

class  UserService{              @reactor.spring.annotation.Selector          def  mailRegistration(User  user){                  sendMail{                          to  user.mail                          subject  "Confirmation"                          html  g.render(template:  "userMailConfirmation")                  }          }  !        @reactor.spring.annotation.Selector("mailRegistration")          def  mailRegistration2(reactor.event.Event  msg){                  sendMail{                          to  msg.data.mail                          subject  "Confirmation"                          html  g.render(template:"userMailConfirmation")                  }          }  }

Grails — Consuming Events (grails-events)

!153

Consume onSelector(method name)

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

class  UserService{              @reactor.spring.annotation.Selector          def  mailRegistration(User  user){                  sendMail{                          to  user.mail                          subject  "Confirmation"                          html  g.render(template:  "userMailConfirmation")                  }          }  !        @reactor.spring.annotation.Selector("mailRegistration")          def  mailRegistration2(reactor.event.Event  msg){                  sendMail{                          to  msg.data.mail                          subject  "Confirmation"                          html  g.render(template:"userMailConfirmation")                  }          }  }

Grails — Consuming Events (grails-events)

!153

Consume onSelector(method name)

Consume on thisspecific topic

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

class  UserService{              @reactor.spring.annotation.Selector          def  mailRegistration(User  user){                  sendMail{                          to  user.mail                          subject  "Confirmation"                          html  g.render(template:  "userMailConfirmation")                  }          }  !        @reactor.spring.annotation.Selector("mailRegistration")          def  mailRegistration2(reactor.event.Event  msg){                  sendMail{                          to  msg.data.mail                          subject  "Confirmation"                          html  g.render(template:"userMailConfirmation")                  }          }  }

Grails — Consuming Events (grails-events)

!153

Consume onSelector(method name)

Consume on thisspecific topic

Event typed signatureto inspect enveloppe

(‘headers…)

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

includes  =  ['default']  !doWithReactor  =  {          reactor(EventsApi.GRAILS_REACTOR)  {                  on('someTopic')  {                          reply  'test'                                          }                                                                                                        }                                                                                                                                        reactor(‘someGormReactor')  {                                                                                                            dispatcher  =  new  SynchronousDispatcher()                  ext  'gorm',  true                                                                                                                  !                stream  {                          consume  {                                  log.info  "Some  gorm  event  is  flowing  with  data  $it.data"                          }.when(Throwable)  {                                  log.error  "Ow  snap!",  it                          }                                                                                                                                                                                                                          }                                                      }                                                                                                                                                                                                }

Grails — A new Artifact conf/XxxxEvents

!154

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

includes  =  ['default']  !doWithReactor  =  {          reactor(EventsApi.GRAILS_REACTOR)  {                  on('someTopic')  {                          reply  'test'                                          }                                                                                                        }                                                                                                                                        reactor(‘someGormReactor')  {                                                                                                            dispatcher  =  new  SynchronousDispatcher()                  ext  'gorm',  true                                                                                                                  !                stream  {                          consume  {                                  log.info  "Some  gorm  event  is  flowing  with  data  $it.data"                          }.when(Throwable)  {                                  log.error  "Ow  snap!",  it                          }                                                                                                                                                                                                                          }                                                      }                                                                                                                                                                                                }

Grails — A new Artifact conf/XxxxEvents

!154

Merge with DefaultsEvents

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

includes  =  ['default']  !doWithReactor  =  {          reactor(EventsApi.GRAILS_REACTOR)  {                  on('someTopic')  {                          reply  'test'                                          }                                                                                                        }                                                                                                                                        reactor(‘someGormReactor')  {                                                                                                            dispatcher  =  new  SynchronousDispatcher()                  ext  'gorm',  true                                                                                                                  !                stream  {                          consume  {                                  log.info  "Some  gorm  event  is  flowing  with  data  $it.data"                          }.when(Throwable)  {                                  log.error  "Ow  snap!",  it                          }                                                                                                                                                                                                                          }                                                      }                                                                                                                                                                                                }

Grails — A new Artifact conf/XxxxEvents

!154

Merge with DefaultsEvents

Build a GroovyEnvironment

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

includes  =  ['default']  !doWithReactor  =  {          reactor(EventsApi.GRAILS_REACTOR)  {                  on('someTopic')  {                          reply  'test'                                          }                                                                                                        }                                                                                                                                        reactor(‘someGormReactor')  {                                                                                                            dispatcher  =  new  SynchronousDispatcher()                  ext  'gorm',  true                                                                                                                  !                stream  {                          consume  {                                  log.info  "Some  gorm  event  is  flowing  with  data  $it.data"                          }.when(Throwable)  {                                  log.error  "Ow  snap!",  it                          }                                                                                                                                                                                                                          }                                                      }                                                                                                                                                                                                }

Grails — A new Artifact conf/XxxxEvents

!154

Merge with DefaultsEvents

Build a GroovyEnvironment

Reactor Groovy Builders

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

includes  =  ['default']  !doWithReactor  =  {          reactor(EventsApi.GRAILS_REACTOR)  {                  on('someTopic')  {                          reply  'test'                                          }                                                                                                        }                                                                                                                                        reactor(‘someGormReactor')  {                                                                                                            dispatcher  =  new  SynchronousDispatcher()                  ext  'gorm',  true                                                                                                                  !                stream  {                          consume  {                                  log.info  "Some  gorm  event  is  flowing  with  data  $it.data"                          }.when(Throwable)  {                                  log.error  "Ow  snap!",  it                          }                                                                                                                                                                                                                          }                                                      }                                                                                                                                                                                                }

Grails — A new Artifact conf/XxxxEvents

!154

Merge with DefaultsEvents

Build a GroovyEnvironment

Reactor Groovy Builders

Grails extension: accept GORM events

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Reactor awesomeness : Selectors

• Listen for arbitrary keys • import static reactor.event.selector.Selectors.*

!155

on(T(SpecificClassType))  {                  reply  it                                  }                                                                                                !

event(new  SpecificClassType(),  'data')                                                                  !

!

on(uri(‘/some/{captureIt}'))  {                                                                                                                        reply  it.headers.captureIt  }                                                                                                                                                                                                                !

event('/some/hourray',  'data')        

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Reactor awesomeness : Selectors

• Listen for arbitrary keys • import static reactor.event.selector.Selectors.*

!155

on(T(SpecificClassType))  {                  reply  it                                  }                                                                                                !

event(new  SpecificClassType(),  'data')                                                                  !

!

on(uri(‘/some/{captureIt}'))  {                                                                                                                        reply  it.headers.captureIt  }                                                                                                                                                                                                                !

event('/some/hourray',  'data')        

Listen for SpecificClassType

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Reactor awesomeness : Selectors

• Listen for arbitrary keys • import static reactor.event.selector.Selectors.*

!155

on(T(SpecificClassType))  {                  reply  it                                  }                                                                                                !

event(new  SpecificClassType(),  'data')                                                                  !

!

on(uri(‘/some/{captureIt}'))  {                                                                                                                        reply  it.headers.captureIt  }                                                                                                                                                                                                                !

event('/some/hourray',  'data')        

Listen for an URI

Listen for SpecificClassType

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Reactor awesomeness : Selectors

• Listen for arbitrary keys • import static reactor.event.selector.Selectors.*

!155

on(T(SpecificClassType))  {                  reply  it                                  }                                                                                                !

event(new  SpecificClassType(),  'data')                                                                  !

!

on(uri(‘/some/{captureIt}'))  {                                                                                                                        reply  it.headers.captureIt  }                                                                                                                                                                                                                !

event('/some/hourray',  'data')        

Listen for an URI

Listen for SpecificClassType

Send using right key type

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Reactor awesomeness : Selectors

• Listen for arbitrary keys • import static reactor.event.selector.Selectors.*

!155

on(T(SpecificClassType))  {                  reply  it                                  }                                                                                                !

event(new  SpecificClassType(),  'data')                                                                  !

!

on(uri(‘/some/{captureIt}'))  {                                                                                                                        reply  it.headers.captureIt  }                                                                                                                                                                                                                !

event('/some/hourray',  'data')        

Listen for an URI

Listen for SpecificClassType

Send using right key type

Send using matching URI

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Reactor awesomeness : Stream API

• Reactive Extensions programming style • Avoid callback hell

!156

withStream  {          event(key:  'test',  data:  1)  }.when(Exception,  {          log.info  "exception:$it"  }).map  {          callExternalService(it)  }.consume('clientKey',  reactor('browser'))

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Reactor awesomeness : Stream API

• Reactive Extensions programming style • Avoid callback hell

!156

withStream  {          event(key:  'test',  data:  1)  }.when(Exception,  {          log.info  "exception:$it"  }).map  {          callExternalService(it)  }.consume('clientKey',  reactor('browser'))

Build a Stream to capture any reply

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Reactor awesomeness : Stream API

• Reactive Extensions programming style • Avoid callback hell

!156

withStream  {          event(key:  'test',  data:  1)  }.when(Exception,  {          log.info  "exception:$it"  }).map  {          callExternalService(it)  }.consume('clientKey',  reactor('browser'))

If a consumer fails

Build a Stream to capture any reply

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Reactor awesomeness : Stream API

• Reactive Extensions programming style • Avoid callback hell

!156

withStream  {          event(key:  'test',  data:  1)  }.when(Exception,  {          log.info  "exception:$it"  }).map  {          callExternalService(it)  }.consume('clientKey',  reactor('browser'))

If a consumer fails

Build a Stream to capture any reply

$(‘test’) consumers may reply

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Reactor awesomeness : Stream API

• Reactive Extensions programming style • Avoid callback hell

!156

withStream  {          event(key:  'test',  data:  1)  }.when(Exception,  {          log.info  "exception:$it"  }).map  {          callExternalService(it)  }.consume('clientKey',  reactor('browser'))

If a consumer fails

Build a Stream to capture any reply

$(‘test’) consumers may reply

Transform result

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Reactor awesomeness : Stream API

• Reactive Extensions programming style • Avoid callback hell

!156

withStream  {          event(key:  'test',  data:  1)  }.when(Exception,  {          log.info  "exception:$it"  }).map  {          callExternalService(it)  }.consume('clientKey',  reactor('browser'))

If a consumer fails

Build a Stream to capture any reply

$(‘test’) consumers may reply

Transform result

Reroute to key ‘clientKey’on Reactor ‘browser’

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Reactor awesomeness: Promise API

• Grails 2.3 Promises become a Reactor Promises • Benefits from Dispatcher overriding • Powerful once Combined with Consumers

!157

dispatcher(ReactorPromise.PromiseDispatcher)  {          type  =  DispatcherType.RingBuffer          backlog  =  512  }

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Reactor awesomeness: Promise API

• Grails 2.3 Promises become a Reactor Promises • Benefits from Dispatcher overriding • Powerful once Combined with Consumers

!157

dispatcher(ReactorPromise.PromiseDispatcher)  {          type  =  DispatcherType.RingBuffer          backlog  =  512  }

task  {          def  res  =  longProcessing()          render  res  }

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Reactor awesomeness : Routing

• During event dispatching, consumers list is selected • Publish Subscribe is the default • Possible to assign different routing strategy

!158

reactor('test1')  {          routingStrategy  'round-­‐robin'          on('test')  {                  reply  '1'          }                        on('test')  {                  reply  '2'          }                }

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Reactor awesomeness : Routing

• During event dispatching, consumers list is selected • Publish Subscribe is the default • Possible to assign different routing strategy

!158

reactor('test1')  {          routingStrategy  'round-­‐robin'          on('test')  {                  reply  '1'          }                        on('test')  {                  reply  '2'          }                }

Will reply 1, 2, 1, 2, 1, 2…

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Extensibility

!

• Main extension points: – Dispatcher, Selector, Registry, EventRouter, Consumer

!

• Metadata in Reactor Events DSL: – ext(‘someExtension’, [ ‘doStuff ’: true ])

!159

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — GORM events

• GORM is now an extension – Using ext(‘gorm’, true) on any candidate reactor – Applicable Selectors: simple topic form (beforeInsert...) – A boolean reply is evaluated as a cancel directive

!160

reactor('someGormReactor'){          dispatcher  =  new  SynchronousDispatcher()          ext  'gorm',  true  }    

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — GORM events

• GORM is now an extension – Using ext(‘gorm’, true) on any candidate reactor – Applicable Selectors: simple topic form (beforeInsert...) – A boolean reply is evaluated as a cancel directive

!160

reactor('someGormReactor'){          dispatcher  =  new  SynchronousDispatcher()          ext  'gorm',  true  }     @Selector(reactor  =  'someGormReactor')  

@ReplyTo  boolean  beforeValidate(Book  b){          false  }

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb !161

GRAILS-­‐EVENTS-­‐PUSH  pluginEventing over HTTP!

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Pattern Examples : “Realtime” web

!162

Browser

ResponseService

Grails Controller RequestService

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Pattern Examples : “Realtime” web

!162

Browser

ResponseService

Grails Controller

Return immediately

RequestService

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Pattern Examples : “Realtime” web

!162

Browser

ResponseService

Grails Controller

Return immediately

RequestService

Trigger async event

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Pattern Examples : “Realtime” web

!162

Browser

ResponseService

Grails Controller

Return immediately

RequestService

Trigger async event

Reply a new event

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Pattern Examples : “Realtime” web

!162

Browser

ResponseService

Grails Controller

Return immediately

RequestService

Trigger async event

Reply a new eventPush reply to browser: Websocket, SSE, long-polling…

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — An elegant solution to browser push

• Powered by Atmosphere 2

• Automatically picks an adapted protocol:

– WebSockets, ServerSideEvent, Streaming, Polling…

• Consumer bridges for server-to-client push

• Reactor bridge for client-to-server push

• Javascript library

!163

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Installing Grails Events Push

1. Install Grails Events plugin 2. Install Grails Events Push plugin

!164

grails.servlet.version  =  "3.0"  grails.tomcat.nio  =  true  !grails.project.dependency.resolution  =  {          repositories  {          //...                  mavenRepo  "https://oss.sonatype.org/content/repositories/snapshots"          }                                            dependencies  {                                  //...                                                    compile  'org.grails.plugins:events:1.0.0.BUILD-­‐SNAPSHOT'          }                                                                                            plugins  {                                                                                            //...                                                                                                    runtime  ":jquery:1.10.2"                                                                                                                                        runtime  ":events-­‐push:1.0.0.BUILD-­‐SNAPSHOT"                                                }  }

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Installing Grails Events Push

1. Install Grails Events plugin 2. Install Grails Events Push plugin

!164

grails.servlet.version  =  "3.0"  grails.tomcat.nio  =  true  !grails.project.dependency.resolution  =  {          repositories  {          //...                  mavenRepo  "https://oss.sonatype.org/content/repositories/snapshots"          }                                            dependencies  {                                  //...                                                    compile  'org.grails.plugins:events:1.0.0.BUILD-­‐SNAPSHOT'          }                                                                                            plugins  {                                                                                            //...                                                                                                    runtime  ":jquery:1.10.2"                                                                                                                                        runtime  ":events-­‐push:1.0.0.BUILD-­‐SNAPSHOT"                                                }  }

Use a “long-connection” friendlytomcat configuration

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — grails-events-push Lifecycle 1 - Handshake

!165

Browser EventsPushHandler

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — grails-events-push Lifecycle 1 - Handshake

!165

Browser EventsPushHandler

Adapt Protocol and Opennew persistent connection

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — grails-events-push Lifecycle 2 - Register

!166

Browser Reactor(s)

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — grails-events-push Lifecycle 2 - Register

!166

Browser Reactor(s)

Bridge a Client Consumer with aremote Reactor Consumer

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — grails-events-push Lifecycle 2 - Register

!166

Browser Reactor(s)

Bridge a Client Consumer with aremote Reactor Consumer

Consumer registered on all Reactor with‘browser’ extension enabled

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — grails-events-push Lifecycle 3 - Notify

!167

Browser ‘browser’ Reactor RequestService

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — grails-events-push Lifecycle 3 - Notify

!167

Browser ‘browser’ Reactor

Notify any key

RequestService

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — grails-events-push Lifecycle 3 - Notify

!167

Browser ‘browser’ Reactor

Notify any key

RequestService

RequestService consumes on matchingSelector within the ‘browser’ Reactor

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — grails-events-push Lifecycle 4 - Consume

!168

Browser Reactor(s) ResponseService

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — grails-events-push Lifecycle 4 - Consume

!168

Browser Reactor(s)

Push event (WS, SSE…)

ResponseService

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — grails-events-push Lifecycle 4 - Consume

!168

Browser Reactor(s)

Push event (WS, SSE…)

ResponseService

Notify ‘browser’ enabledReactor and match key

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Generating Simple Browsers bridges

!169

includes  =  'push'  !doWithReactor  =  {          reactor('browser')  {                  ext  'browser',  [                          'control',                          'move',                          'fire',                          'leave'                  ]          }          reactor(‘grailsReactor')  {                  ext  'browser',  ['sleepBrowser']  !                stream(‘sleepBrowser')  {                          filter  {                                    it.data  ==  'no'                          }                  }          }  }

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Generating Simple Browsers bridges

!169

includes  =  'push'  !doWithReactor  =  {          reactor('browser')  {                  ext  'browser',  [                          'control',                          'move',                          'fire',                          'leave'                  ]          }          reactor(‘grailsReactor')  {                  ext  'browser',  ['sleepBrowser']  !                stream(‘sleepBrowser')  {                          filter  {                                    it.data  ==  'no'                          }                  }          }  }

Bridge browser to these reactorconsumers $(‘control),…

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Generating Simple Browsers bridges

!169

includes  =  'push'  !doWithReactor  =  {          reactor('browser')  {                  ext  'browser',  [                          'control',                          'move',                          'fire',                          'leave'                  ]          }          reactor(‘grailsReactor')  {                  ext  'browser',  ['sleepBrowser']  !                stream(‘sleepBrowser')  {                          filter  {                                    it.data  ==  'no'                          }                  }          }  }

Bridge browser to these reactorconsumers $(‘control),…

Conventional name tooverride ‘browser’ reactor

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Generating Simple Browsers bridges

!169

includes  =  'push'  !doWithReactor  =  {          reactor('browser')  {                  ext  'browser',  [                          'control',                          'move',                          'fire',                          'leave'                  ]          }          reactor(‘grailsReactor')  {                  ext  'browser',  ['sleepBrowser']  !                stream(‘sleepBrowser')  {                          filter  {                                    it.data  ==  'no'                          }                  }          }  }

Bridge browser to these reactorconsumers $(‘control),…

Conventional name tooverride ‘browser’ reactor

Bridge browser to this reactorconsumer $(‘sleepBrowser)

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Generating Simple Browsers bridges

!169

includes  =  'push'  !doWithReactor  =  {          reactor('browser')  {                  ext  'browser',  [                          'control',                          'move',                          'fire',                          'leave'                  ]          }          reactor(‘grailsReactor')  {                  ext  'browser',  ['sleepBrowser']  !                stream(‘sleepBrowser')  {                          filter  {                                    it.data  ==  'no'                          }                  }          }  }

Bridge browser to these reactorconsumers $(‘control),…

Conventional name tooverride ‘browser’ reactor

Bridge browser to this reactorconsumer $(‘sleepBrowser)

Prevent data ‘no’ to bedispatched to browser bridges

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — React on server side events

!170

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — React on server side events

!170

Inject grailsEvents.js (requires resources plugin)

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — React on server side events

!170

Inject grailsEvents.js (requires resources plugin)

Create a connectionbetween the browser and

the current Grails app

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — React on server side events

!170

Inject grailsEvents.js (requires resources plugin)

Create a connectionbetween the browser and

the current Grails app

Listen for $(‘afterInsert’) events

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Generating Advanced Browsers bridges

!171

includes  =  ['push']  !doWithReactor  =  {          reactor(‘grailsReactor')  {                  ext  'gorm',  true                            ext  'browser',  [                                                                                                                                                                                                                    'test':  true,                      'afterInsert':  [                                        browserFilter:  {m,  r  -­‐>  true  }                                                                                                                                                                              ],                                                                                                                                                                                                                                                      (R("sampleBro-­‐.*"))  :  true,                  ]                                                                                                                                                                                                  }                                                                                                                                                                                                                                                                                                                      reactor(EventsPushConstants.FROM_BROWSERS)  {                  ext  'browser',  [R("sampleBro-­‐.*")]                                                                                                                                                }                                                                                                            }  

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Generating Advanced Browsers bridges

!171

includes  =  ['push']  !doWithReactor  =  {          reactor(‘grailsReactor')  {                  ext  'gorm',  true                            ext  'browser',  [                                                                                                                                                                                                                    'test':  true,                      'afterInsert':  [                                        browserFilter:  {m,  r  -­‐>  true  }                                                                                                                                                                              ],                                                                                                                                                                                                                                                      (R("sampleBro-­‐.*"))  :  true,                  ]                                                                                                                                                                                                  }                                                                                                                                                                                                                                                                                                                      reactor(EventsPushConstants.FROM_BROWSERS)  {                  ext  'browser',  [R("sampleBro-­‐.*")]                                                                                                                                                }                                                                                                            }  

Include PushEvents (configure areactor for events from Browsers)

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Generating Advanced Browsers bridges

!171

includes  =  ['push']  !doWithReactor  =  {          reactor(‘grailsReactor')  {                  ext  'gorm',  true                            ext  'browser',  [                                                                                                                                                                                                                    'test':  true,                      'afterInsert':  [                                        browserFilter:  {m,  r  -­‐>  true  }                                                                                                                                                                              ],                                                                                                                                                                                                                                                      (R("sampleBro-­‐.*"))  :  true,                  ]                                                                                                                                                                                                  }                                                                                                                                                                                                                                                                                                                      reactor(EventsPushConstants.FROM_BROWSERS)  {                  ext  'browser',  [R("sampleBro-­‐.*")]                                                                                                                                                }                                                                                                            }  

Include PushEvents (configure areactor for events from Browsers)

Listen on GORM events

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Generating Advanced Browsers bridges

!171

includes  =  ['push']  !doWithReactor  =  {          reactor(‘grailsReactor')  {                  ext  'gorm',  true                            ext  'browser',  [                                                                                                                                                                                                                    'test':  true,                      'afterInsert':  [                                        browserFilter:  {m,  r  -­‐>  true  }                                                                                                                                                                              ],                                                                                                                                                                                                                                                      (R("sampleBro-­‐.*"))  :  true,                  ]                                                                                                                                                                                                  }                                                                                                                                                                                                                                                                                                                      reactor(EventsPushConstants.FROM_BROWSERS)  {                  ext  'browser',  [R("sampleBro-­‐.*")]                                                                                                                                                }                                                                                                            }  

Include PushEvents (configure areactor for events from Browsers)

Listen on GORM events

Extension to bridgeBrowsers consumers to

Server-side pair

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Generating Advanced Browsers bridges

!171

includes  =  ['push']  !doWithReactor  =  {          reactor(‘grailsReactor')  {                  ext  'gorm',  true                            ext  'browser',  [                                                                                                                                                                                                                    'test':  true,                      'afterInsert':  [                                        browserFilter:  {m,  r  -­‐>  true  }                                                                                                                                                                              ],                                                                                                                                                                                                                                                      (R("sampleBro-­‐.*"))  :  true,                  ]                                                                                                                                                                                                  }                                                                                                                                                                                                                                                                                                                      reactor(EventsPushConstants.FROM_BROWSERS)  {                  ext  'browser',  [R("sampleBro-­‐.*")]                                                                                                                                                }                                                                                                            }  

Include PushEvents (configure areactor for events from Browsers)

Listen on GORM events

Extension to bridgeBrowsers consumers to

Server-side pair

Override “From Browsers” reactorand bridge consumers

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Generating Advanced Browsers bridges

!171

includes  =  ['push']  !doWithReactor  =  {          reactor(‘grailsReactor')  {                  ext  'gorm',  true                            ext  'browser',  [                                                                                                                                                                                                                    'test':  true,                      'afterInsert':  [                                        browserFilter:  {m,  r  -­‐>  true  }                                                                                                                                                                              ],                                                                                                                                                                                                                                                      (R("sampleBro-­‐.*"))  :  true,                  ]                                                                                                                                                                                                  }                                                                                                                                                                                                                                                                                                                      reactor(EventsPushConstants.FROM_BROWSERS)  {                  ext  'browser',  [R("sampleBro-­‐.*")]                                                                                                                                                }                                                                                                            }  

Include PushEvents (configure areactor for events from Browsers)

Listen on GORM events

Extension to bridgeBrowsers consumers to

Server-side pair

Override “From Browsers” reactorand bridge consumersEvents are only routed, from

Browsers to bridged Browsers

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — The Bad Stuff

• events-si : Events API on top of Spring Integration – Not here (just) yet

!

• events-vertx : Abandoned experiment – Working around Distributed Reactor

!

• Stream DSL could be optimized – Reducing the number of objects

!172

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Roadmap

• events: – Document, especially for Migration from Platform Core – Stick with latest awesome features from Reactor • Still Many API to expose: Processors, Buffer, TCP, Queues,

Sequencer… !

• events-push -> events-atmosphere : – Add support for replyTo – extract to standalone module : reactor-atmosphere

!173

@glaforge — @smaldini / #DV13-rtweb@glaforge — @smaldini / #DV13-rtweb

Grails — Roadmap

• events-sockjs: – Involves Reactor work here

!

• events-si: – Supports new events plugin

!

• events-xx: – The plugin where you are the hero

!174

Demo

Summary

Part 4

@glaforge — @smaldini / #DV13-rtweb

Groovy, Reactor, Grails and the realtime web

• Embrace modern reactive architectures with Groovy, Reactor and Grails!

• Groovy is a versatile language, that enables development of concise and functional oriented applications

• Reactor fuels your asynchronous and reactive applications with its ultra fast dispatching engine and non-blocking model.

• Grails supports your today's and tomorrow's web app design, tooled with the right plugins you are prepared for responsive and interactive applications

!177