Post on 10-May-2015
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