vert.x using groovy - simplifying non-blocking code
DESCRIPTION
The possibilities and advantages of non-blocking IO are great. But as you have to hassle with callbacks all over the place you have to think differently. Sometimes simple constructs we are used to are getting ugly or really hard to realize. A little bit of Groovy-magic can help out to simplify life and make your code more look like you are used to. This session wants to show experiences creating a vert.x-based application and the solutions we used to smooth up our code.TRANSCRIPT
![Page 1: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/1.jpg)
Alexander (Sascha) Kleincodecentric AG
vert.x with GroovySimplifying non-blocking code
![Page 2: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/2.jpg)
codecentric AG
Alexander Klein, 2014-06-03
vert.x with Groovy – Simpliyfing non-blocking code
![Page 3: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/3.jpg)
codecentric AG
Why using vert.x ?
CC BY 2.0 > http://www.flickr.com/photos/girliemac/6509400997
![Page 4: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/4.jpg)
codecentric AG
Alexander (Sascha) Klein
Principal Consultant
codecentric AG in Stuttgart
Germany
Groovy, JavaFX, UI / UX
Griffon committer
@saschaklein
http://gplus.to/karfunkel
![Page 5: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/5.jpg)
codecentric AG
vert.x
Framework to write polyglot, highly concurrent applications
Similar to Node.js
Asynchronous, non-blocking API
Polyglot (Java, JavaScript, Groovy, Ruby, Python and others)
![Page 6: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/6.jpg)
codecentric AG
Architecture
Client Background ThreadpoolWorker-Verticle
Worker-Verticle
Worker-Verticle
Event Loop
Verticle
Verticle
Verticle
Event Bus
Request
Response
delegating
long-running tasks
non-blocking blocking
![Page 7: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/7.jpg)
codecentric AG
Yoke
Middleware framework for vert.x
Currently only Java, JavaScript and Groovy supported
Many helpful implementations
Request body and Cookie parser
Static file server
Request Router
Virtual host support
Templateengines
and more ...
![Page 8: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/8.jpg)
codecentric AG
Calculating CRC32's for a directory
Read directory entries
Read file properties for each entry
Determine if entry is a directory
Handle directories recursively
Read file
Calculate CRC32 via worker verticle
![Page 9: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/9.jpg)
codecentric AG
Classic vert.x/yoke code
container.deployWorkerVerticle 'CRC.groovy', [:]
GRouter router = new GRouter()
router.get("/crc") { GYokeRequest request ->
request.response.chunked = true
request.response.contentType = 'text/plain'
this.crc('/home/aklein/tmp/ConfigParser', request)
}
router.get("/") { GYokeRequest request, Handler next ->
request.response.render 'web/index.gsp', next
}
![Page 10: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/10.jpg)
codecentric AG
Classic vert.x/yoke code
def yoke = new GYoke(vertx, container)
yoke.engine new GroovyTemplateEngine()
yoke.use(router)
yoke.use new Static("web", 24 * 60 * 60 * 1000, true, false)
yoke.use { request ->
request.response.statusCode = 404
request.response.statusMessage = 'Not Found'
request.response.contentType = 'text/plain'
request.response.end('404 - Not Found')
}
yoke.listen(8080)
![Page 11: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/11.jpg)
codecentric AG
Classic vert.x/yoke code
def crc(String baseDir, GYokeRequest request) {
EventBus bus = vertx.eventBus
FileSystem fs = vertx.fileSystem
fs.readDir(baseDir) { AsyncResult<String[]> rs ->
if (rs.succeeded) {
String[] paths = rs.result
paths.each { String path ->
fs.props(path) { AsyncResult<FileProps> rs1 ->
if (rs1.succeeded) {
FileProps props = rs1.result
if (props.directory) {
crc(path, request)
} else {
fs.readFile(path) { AsyncResult<Buffer> rs2 ->
if (rs2.succeeded) {
Buffer content = rs2.result
bus.send("create.crc", content) { Message result ->
if (result.body().status == 'error') {
request.response.statusCode = 500
request.response.statusMessage = "Error processing file " +
"$path: ${result.body().message}: ${result.body().error} \n" +
"${result.body().stacktrace}"
request.response.end()
} else {
request.response.write "$path = ${result.body().message}\n"
}
}
} else {
request.response.statusCode = 500
request.response.statusMessage = "Failed to read file $path"
request.response.end()
}
}
}
} else {
request.response.statusCode = 500
request.response.statusMessage = "Failed to read properties for $path"
request.response.end()
}
}
}
} else {
request.response.statusCode = 500
request.response.statusMessage = "Failed to read $baseDir"
request.response.end()
}
}
}
![Page 12: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/12.jpg)
codecentric AG
Preparing gradle build
Download from: http://github.com/vert-x/vertx-gradle-template
build.gradle
provided "com.jetdrone:yoke:$yokeVersion@jar" // (optional for using yoke)
gradle.properties
groovyVersion=2.2.1
yokeVersion=1.0.13 // (optional for using yoke)
![Page 13: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/13.jpg)
codecentric AG
Preparing gradle build
gradle/vertx.gradle
task startMod(dependsOn: copyMod, description: 'Run the module', type: JavaExec) {
classpath = sourceSets.main.compileClasspath + sourceSets.main.runtimeClasspath
main = 'org.vertx.java.platform.impl.cli.Starter'
args(['runmod', moduleName])
args runModArgs.split("\\s+")
// jvmArgs "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"
systemProperties([
"vertx.clusterManagerFactory": "org.vertx.java.spi.cluster.impl.hazelcast.HazelcastClusterManagerFactory",
"vertx.mods" : "$projectDir/build/mods"
])
}
![Page 14: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/14.jpg)
codecentric AG
Classic vert.x/yoke code
def crc(String baseDir, GYokeRequest request) {
EventBus bus = vertx.eventBus
FileSystem fs = vertx.fileSystem
fs.readDir(baseDir) { AsyncResult<String[]> rs ->
if (rs.succeeded) {
String[] paths = rs.result
paths.each { String path ->
fs.props(path) { AsyncResult<FileProps> rs1 ->
if (rs1.succeeded) {
FileProps props = rs1.result
if (props.directory) {
crc(path, request)
} else {
fs.readFile(path) { AsyncResult<Buffer> rs2 ->
if (rs2.succeeded) {
Buffer content = rs2.result
bus.send("create.crc", content) { Message result ->
if (result.body().status == 'error') {
request.response.statusCode = 500
request.response.statusMessage = "Error processing file " +
"$path: ${result.body().message}: ${result.body().error} \n" +
"${result.body().stacktrace}"
request.response.end()
} else {
request.response.write "$path = ${result.body().message}\n"
}
}
} else {
request.response.statusCode = 500
request.response.statusMessage = "Failed to read file $path"
request.response.end()
}
}
}
} else {
request.response.statusCode = 500
request.response.statusMessage = "Failed to read properties for $path"
request.response.end()
}
}
}
} else {
request.response.statusCode = 500
request.response.statusMessage = "Failed to read $baseDir"
request.response.end()
}
}
}
![Page 15: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/15.jpg)
codecentric AG
Compress Errorhandling - Method
request.response.statusCode = 500
request.response.statusMessage = "Failed to read file $path"
request.response.end()
------------------------------------------------------------------------------------------------------------------------------------------------------------------
def end(YokeResponse response, int statusCode, String statusMessage = null) {
response.statusCode = statusCode
if(statusMessage)
response.statusMessage = statusMessage
response.end()
}
------------------------------------------------------------------------------------------------------------------------------------------------------------------
end request.response, 500, "Failed to read file $path"
![Page 16: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/16.jpg)
codecentric AG
Compress Errorhandling - Dynamic Mixins
class YokeExtension {
static String end(YokeResponse self, Integer statusCode, String statusMessage = null) {
self.statusCode = statusCode
if (statusMessage)
self.statusMessage = statusMessage
self.end()
}
}
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
YokeResponse.mixin(YokeExtension)
request.response.end 500, "Failed to read file $path"
![Page 17: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/17.jpg)
codecentric AG
Compress Errorhandling - Static Mixins (vert.x 2.1)
class YokeExtension {
static String end(YokeResponse self, Integer statusCode, String statusMessage = null) {
...
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
compilerConfiguration.groovy:
customizer = { org.codehaus.groovy.control.CompilerConfiguration config ->
config.addCompilationCustomizers(
new ASTTransformationCustomizer(Mixin, value: YokeExtension) )
return config
}
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
request.response.end 500, "Failed to read file $path"
![Page 18: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/18.jpg)
codecentric AG
Compress Errorhandling - Module Extension (vert.x 2.1)
class YokeExtension {
static String end(YokeResponse self, Integer statusCode, String statusMessage = null) {
...
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
META-INF/services/org.codehaus.groovy.runtime.ExtensionModule:
moduleName = vertx-module
moduleVersion = 1.0
extensionClasses = de.codecentric.vertx.YokeExtension
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
build.gradle:
repositories {
mavenLocal()
}
dependencies {
compile "de.codecentric:vertx-extension:1.0.0-SNAPSHOT@jar"
}
![Page 19: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/19.jpg)
codecentric AG
After YokeResponse enhancement
def crc(String baseDir, GYokeRequest request) {
EventBus bus = vertx.eventBus
FileSystem fs = vertx.fileSystem
fs.readDir(baseDir) { AsyncResult<String[]> rs ->
if (rs.succeeded) {
String[] paths = rs.result
paths.each { String path ->
fs.props(path) { AsyncResult<FileProps> rs1 ->
if (rs1.succeeded) {
FileProps props = rs1.result
if (props.directory) {
crc(path, request)
} else {
fs.readFile(path) { AsyncResult<Buffer> rs2 ->
if (rs2.succeeded) {
Buffer content = rs2.result
bus.send("create.crc", content) { Message result ->
if (result.body().status == 'error') {
request.response.end 500, "Error processing file " +
"$path: ${result.body().message}: ${result.body().error} \n" +
"${result.body().stacktrace}"
} else
request.response.write "$path = ${result.body().message}\n"
}
} else
request.response.end 500, "Failed to read file $path"
}
}
} else
request.response.end 500, "Failed to read properties for $path"
}
}
} else
request.response.end 500, "Failed to read $baseDir“
}
}
![Page 20: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/20.jpg)
codecentric AG
Bus communication
if (rs2.succeeded) {
Buffer content = rs2.result
bus.send("create.crc", content) { Message result ->
if (result.body().status == 'error') {
request.response.end 500, "Error processing file " +
"$path: ${result.body().message}: ${result.body().error} \n" +
"${result.body().stacktrace}"
} else
request.response.write "$path = ${result.body().message}\n"
}
} else
request.response.end 500, "Failed to read file $path"
![Page 21: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/21.jpg)
codecentric AG
Worker Module
EventBus bus = vertx.eventBus
bus.registerHandler('create.crc') { Message msg ->
try {
Buffer buffer = new Buffer(msg.body())
CRC32 crc = new CRC32()
int start = 0, end, length = buffer.length
while (start < length) {
end = Math.min(start + 1024, length)
crc.update(buffer.getBytes(start, end))
start = end
}
msg.reply([status: 'ok', message: crc.value ])
} catch (e) {
StringWriter sw = new StringWriter()
e.printStackTrace(sw.newPrintWriter())
msg.reply([status: 'error', message: 'Failure creating crc', error: e.message, stacktrace: sw.toString()])
}
}
![Page 22: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/22.jpg)
codecentric AG
Standardizing bus communication – Worker
bus.registerHandler('create.crc') { Message msg ->
try { ...
msg.reply([status: 'ok', message: crc.value ])
} catch (e) {
StringWriter sw = new StringWriter()
e.printStackTrace(sw.newPrintWriter())
msg.reply([status: 'error', message: 'Failure creating crc', error: e.message, stacktrace: sw.toString()])
}
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
bus.registerHandler('create.crc') { Message msg ->
try { ...
msg.replySuccess(crc.value)
} catch (e) {
msg.replyFailure('Failure creating crc', e)
}
}
![Page 23: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/23.jpg)
codecentric AG
Standardizing bus communication - Module
class MessageExtension {
static final String OK = 'ok'
static final String ERROR = 'error'
static void replySuccess(Message self, message) {
self.reply([status: OK, message: message])
}
static void replyFailure(Message self, Throwable e) {
replyFailure(self, null, e)
}
static void replyFailure(Message self, String msg, Throwable e = null) {
def message = [status: ERROR]
if (msg)
message.message = msg
if (e) {
message.error = e.message
StringWriter sw = new StringWriter()
e.printStackTrace(sw.newPrintWriter())
message.stacktrace = sw.toString()
}
self.reply(message)
}
![Page 24: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/24.jpg)
codecentric AG
Standardizing bus communication - Module
static String getStacktrace(Message self) {
self.body().stacktrace
}
static String getError(Message self) {
self.body().error
}
static def getMessage(Message self) {
return self.body().message
}
...
![Page 25: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/25.jpg)
codecentric AG
Standardizing bus communication – Caller
bus.send("create.crc", content) { Message result ->
if (result.body().status == 'error')
request.response.end 500,
"Error processing file $path: ${result.body().message}: ${result.body().error} \n $result.body().stacktrace}"
else
request.response.write "$path = ${result.body().message}\n"
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
bus.send("create.crc", content) { Message result ->
if (result)
request.response.write "$path = ${result.message}\n"
else
request.response.end 500, "Error processing file $path: $result.logMessage"
}
![Page 26: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/26.jpg)
codecentric AG
Standardizing bus communication - Module
static boolean isSucceeded(Message self) {
def result = self.body()
if (result instanceof Map) {
return result.status == OK
} else
return false
}
static boolean asBoolean(Message self) {
return self.isSucceeded()
}
static String getLogMessage(Message self) {
return self.getError() ? "${self.getMessage()}: ${self.getError()} \n${self.getStacktrace()}" : self.getMessage()
}
...
![Page 27: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/27.jpg)
codecentric AG
Streamlining AsyncResult API
static boolean asBoolean(AsyncResult self) {
return self.isSucceeded()
}
static String getStacktrace(AsyncResult self) {
if (!self.cause)
return ''
StringWriter sw = new StringWriter()
PrintWriter pw = sw.newPrintWriter()
self.cause.printStackTrace(pw)
return sw.toString()
}
static String getError(AsyncResult self) {
return self.cause ? self.cause.message : ''
}
static def getMessage(AsyncResult self) {
return self.result
}
static String getLogMessage(AsyncResult self) {
return self.getError() ? self.getMessage() +
": ${self.getError()} \n${self.getStacktrace()}" :
self.getMessage()
}
![Page 28: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/28.jpg)
codecentric AG
With standardized bus communication
def crc(String baseDir, GYokeRequest request) {
EventBus bus = vertx.eventBus
FileSystem fs = vertx.fileSystem
fs.readDir(baseDir) { AsyncResult<String[]> rs ->
if (rs) {
String[] paths = rs.result
paths.each { String path ->
fs.props(path) { AsyncResult<FileProps> rs1 ->
if (rs1) {
FileProps props = rs1.result
if (props.directory) {
crc(path, request)
} else {
fs.readFile(path) { AsyncResult<Buffer> rs2 ->
if (rs2) {
Buffer content = rs2.result
bus.send("create.crc", content) { Message result ->
if (result) {
request.response.write "$path = ${result.message}\n"
} else
request.response.end 500, "Error processing file $path:" + result.logMessage
} else
request.response.end 500, "Failed to read file $path"
}
}
} else
request.response.end 500, "Failed to read properties for $path"
}
}
} else
request.response.end 500, "Failed to read $baseDir“
}
}
![Page 29: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/29.jpg)
codecentric AG
Handler chains
Event-based programming often results in multiple, stacked Handlers / Closures
Difficult to read
Order of commands from left to right / outside to inside
Horizontal scrolling because of indentation
Hard to find the begining of a logical part
Difficult to test
Loops are difficult or impossible to implement
When is the for loop finished to send the .end()?
![Page 30: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/30.jpg)
codecentric AG
Closure Chaining - Syntax
chain { next -> next() }, { next -> next(10) }, { input, next -> println input }
chain ( 10, { input, next -> next(input) }, { input, next -> println input } )
chain 10, { input, next -> next(input) }, { input, next -> println input }
------------------------------------------------------------------------------------------------------------------------------------------------------------------
chain { next -> next() } { next -> next(10) } { input, next -> println input }
chain (10) { input, next -> next(input) } { input, next -> println input }
chain (10) { input, next ->
next(input)
} {
input, next -> println input
}
![Page 31: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/31.jpg)
codecentric AG
Closure Chaining – Module
class StructureExtension {
static void chain(final Object self, def arguments, Closure... actions) {
if (arguments instanceof Closure) {
actions = [arguments, *actions] as Closure[]
arguments = null
}
if (!actions)
throw new IllegalArgumentException("One or more arguments of type groovy.lang.Closure required")
_chain(arguments, actions.iterator())
}
...chain{} …chain(arg)chain(arg) {} ...
![Page 32: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/32.jpg)
codecentric AG
Closure Chaining – Module
static void chain(final Object self, Object... arguments) {
if (!arguments.any { it instanceof Closure })
throw new IllegalArgumentException("One or more arguments of type groovy.lang.Closure required")
int i; def actions = []
for (i = arguments.size() - 1; i >= 0; i--) {
if (arguments[i] instanceof Closure)
actions.add(0, arguments[i])
else
break
}
_chain(arguments[0..i], actions.iterator())
}
...
chain()chain(arg1, arg2, ...)chain(arg1, arg2, ...) {} …
![Page 33: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/33.jpg)
codecentric AG
Closure Chaining – Module
private static void _chain(final Object arguments, final Iterator<Closure> actions) {
if (actions) {
def action = actions.next()
if (arguments != null) {
action = action.curry(arguments as Object[])
}
action.call { Object[] args ->
_chain(args, actions)
}
}
}
...
![Page 34: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/34.jpg)
codecentric AG
Looping - Syntax
[1,2,3].loop { element, next -> next() }
[a:1, b:2, c:3].loop { key, value, next -> next() }
[1,2,3].loop { element, next ->
next()
} {
// called after the last iteration
}
![Page 35: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/35.jpg)
codecentric AG
Looping – Module
static void loop(final Object[] array, final Closure action) { loop(array, action, {} }
static void loop(final Object[] array, final Closure action, final Closure next) { _loop(array?.iterator(), action, next) }
static void loop(final Collection collection, final Closure action) { loop(collection, action, {} }
static void loop(final Collection collection, final Closure action, final Closure next) {
_loop(collection.iterator(), action, next)
}
static void loop(final Map map, final Closure action) { loop(map, action, {} }
static void loop(final Map map, final Closure action, final Closure next) { _loop(map.iterator(), action, next) }
...
![Page 36: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/36.jpg)
codecentric AG
Looping – Module
private static void _loop(final Iterator<?> iterator, final Closure action, Closure next = {}) {
if(iterator) {
def element = iterator.next()
def nextAction
if (iterator)
nextAction = StructureExtension.&_loop.curry(iterator, action, next)
else
nextAction = next
if (element instanceof Map.Entry)
action.call(element.key, element.value, nextAction)
else
action.call(element, nextAction)
} else next.call()
}
![Page 37: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/37.jpg)
codecentric AG
With chaining and looping
def crc(String baseDir, GYokeRequest request, Closure nextCrc = null) {
FileSystem fs = vertx.fileSystem
chain { nextChain -> // Read directory
fs.readDir(baseDir) { AsyncResult<String[]> rs ->
if (rs) nextChain(rs.result as List)
else request.response.end 500, "Failed to read $baseDir"
}
} { List paths, nextChain -> // Loop over files
paths.loop { String path, nextLoop ->
chain { next -> // Read file properties
fs.props(path) { AsyncResult<FileProps> rs ->
if (rs) next(rs.result)
else request.response.end 500, "Failed to read properties for $path"
}
} { FileProps props, next -> // Check for directory
if (props.directory) crc(path, request, nextLoop)
else next()
}
{ next -> // Read file
fs.readFile(path) { AsyncResult<Buffer> rs ->
if (rs) next(rs.result)
else request.response.end 500, "Failed to read file $path"
}
} { Buffer content, next -> // Call module to calculate crc
bus.send("create.crc", content) { Message result ->
if (result) {
request.response.write "$path = ${result.message}\n"
nextLoop()
} else request.response.end 500, "Error processing file $path"
}
}
}
}
}
![Page 38: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/38.jpg)
codecentric AG
Adding .end() after the loop
{ Buffer content, next -> // Call module to calculate crc
Vertx.eventBus.send("create.crc", content) { Message result ->
if (result) {
request.response.write "$path = ${result.message}\n"
nextLoop()
} else request.response.end 500, "Error processing file $path"
}
}
} { // finish everything up after loop
if (nextCrc) nextCrc()
else request.response.end()
}
}
}
![Page 39: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/39.jpg)
codecentric AG
Using a template engine
router.get("/crc") { GYokeRequest request ->
request.context.files = [:]
...
def crc(String baseDir, GYokeRequest request, Closure nextCrc = null) {
request.context.files[baseDir] = null
...
{ Buffer content, next -> // Call module to calculate crc
Vertx.eventBus.send("create.crc", content) { Message result ->
if (result) {
request.context.files[path] = result.message
nextLoop()
} else request.response.end 500, "Error processing file $path"
}
}
}
...
} { // finish everything up after loop
if (nextCrc) nextCrc()
else
request.response.render('web/crc.gsp',
files: request.context.files)
}
}
}
![Page 40: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/40.jpg)
codecentric AG
Accessing the context - !!! Hack Alert !!!
class YokeExtension {
...
static Context getContext(YokeRequest self) {
// Reflection because context is a private field of the super class for GYokeRequest
Field field = YokeRequest.getDeclaredField('context')
field.accessible = true
return (Context) field.get(self)
}
static Context getContext(YokeResponse self) {
// Reflection because context is a private field of the super class for GYokeResponse
Field field = YokeResponse.getDeclaredField('context')
field.accessible = true
return (Context) field.get(self)
}
![Page 41: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/41.jpg)
codecentric AG
Adding custom context for rendering
static void render(GYokeResponse self, Map<String, Object> context, String template) { render(self, context, template, null, null) }
static void render(GYokeResponse self, Map<String, Object> context, String template, Closure next) {
render(self, context, template, null, next)
}
static void render(GYokeResponse self, Map<String, Object> context, String template, String layoutTemplate) {
render(self, context, template, layoutTemplate, null)
}
static void render(GYokeResponse self, Map<String, Object> context, String template, String layoutTemplate, Closure next) {
Map<String, Object> oldContext = getContext(self).clone()
getContext(self).clear()
getContext(self).putAll(context)
if (next)
self.render(template, layoutTemplate, next)
else
self.render(template, layoutTemplate)
getContext(self).clear()
getContext(self).putAll(oldContext)
}
![Page 42: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/42.jpg)
codecentric AG
The template
<html>
<head>
<title>CRC</title>
</head>
<body>
<ul>
<% def data = files.sort { a, b -> a.key <=> b.key }
data.each { k, v ->
if (v != null) { %>
<li>${k} = ${v}</li>
<% } else { %>
<li>${k}</li>
<% } %>
<% } %>
</ul>
</body>
</html>
![Page 43: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/43.jpg)
codecentric AG
Smoothen all up with a custom BaseScriptClass
abstract class VerticleScript extends Script {
Vertx getVertx() {
return binding.vertx
}
void setVertx(Vertx vertx) {
binding.vertx = vertx
}
Container getContainer() {
return binding.container
}
void setContainer(Container container) {
binding.container = container
}
EventBus getBus() {
vertx.eventBus
}
SharedData getSharedData() {
vertx.sharedData
}
Logger getLog() {
container.logger
}
Map<String, Object> getConfig() {
container.config
}
Map<String, String> getEnv() {
container.env
}
![Page 44: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/44.jpg)
codecentric AG
Using the BaseScriptClass (vert.x 2.1)
Global usage:
compilerConfiguration.groovy:
customizer = { org.codehaus.groovy.control.CompilerConfiguration config ->
config.scriptBaseClass = 'de.codecentric.vertx.VerticleScript'
return config
}
------------------------------------------------------------------------------------------------------------------------------------------------------------------
Local usage per Script:
@groovy.transform.BaseScript de.codecentric.vertx.VerticleScript verticleScript
![Page 45: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/45.jpg)
codecentric AG
API to smoothen MongoDB usage
def db(String address, Map message, Closure success = null, Closure failure = null) {
bus.send(address, message) { Message result ->
Map reply = result.body()
if (reply.status == 'ok') {
if (success) {
if (success.maximumNumberOfParameters == 2) success(reply, result)
else success(reply)
}
} else {
if (failure) {
if (failure.maximumNumberOfParameters == 2) failure(reply, result)
else failure(result)
}
}
}
}
![Page 46: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/46.jpg)
codecentric AG
API to smoothen MongoDB usage
def save(String address, String collection, Map document, Closure success = null, Closure failure = null) {
db(address,
[action: 'save', collection: collection, document: document, write_concern: 'SAFE'],
success, failure)
}
def update(String address, String collection, Map criteria, Map update, Closure success=null, Closure failure=null) {
db(address, [action: 'update', collection: collection, criteria: criteria, objNew: update, write_concern: 'SAFE'],
success, failure)
}
def delete(String address, String collection, Map matcher, Closure success = null, Closure failure = null) {
db(address, [action: 'delete', collection: collection, matcher: matcher], success, failure)
}
def read(String address, String collection, Map matcher, Closure success = null, Closure failure = null) {
db(address, [action: 'findone', collection: collection, matcher: matcher,], success, failure)
}
![Page 47: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/47.jpg)
codecentric AG
API to smoothen MongoDB usage
def exists(String address, String collection, Map matcher, Closure success = null, Closure failure = null) {
def command = [action: 'find', collection: collection, matcher: matcher, batch_size: 100]
db(address, command, success) { Map reply, Message result ->
if (reply.status == 'more-exist') {
if (success.maximumNumberOfParameters == 2)
success(reply, result)
else
success(result)
} else {
if (failure.maximumNumberOfParameters == 2)
failure(reply, result)
else
failure(result)
}
}
}
![Page 48: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/48.jpg)
codecentric AG
API to smoothen MongoDB usage
def query(String address, String collection, Map matcher,
Map options, Closure success, Closure failure) {
int max = options.max ?: -1
int offset = options.offset ?: -1
Map orderby = options.orderby ?: null
Map keys = options.keys ?: null
def data = []
def queryHandler
queryHandler = { Map reply, Message result ->
if (reply.status == 'more-exist') {
data.addAll reply.results
result.reply([:], queryHandler)
} else if (reply.status == 'ok') {
data.addAll reply.results
success(data)
} else if (reply.status == 'ok') {
data.addAll reply.results
success(data)
} else if (failure.maximumNumberOfParameters == 2) {
failure(reply, result)
} else failure(result)
}
def command = [ action: 'find', collection: collection, matcher : matcher, batch_size: 100]
if (max >= 0) command.max = max
if (offset >= 0) command.offset = offset
if (orderby) command.orderby = orderby
if (keys) command.keys = keys
db(address, command, queryHandler, queryHandler)
}
![Page 49: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/49.jpg)
codecentric AG
API to smoothen MongoDB usage
def query(String address, String collection, Map matcher, Closure success) {
query(address, collection, matcher, [:], success, null)
}
def query(String address, String collection, Map matcher, Closure success, Closure failure) {
query(address, collection, matcher, [:], success, failure)
}
def query(String address, String collection, Map matcher, Map options, Closure success) {
query(address, collection, matcher, options, success, null)
}
![Page 50: Vert.x using Groovy - Simplifying non-blocking code](https://reader034.vdocuments.site/reader034/viewer/2022042518/554f6d58b4c9058a148b504b/html5/thumbnails/50.jpg)
codecentric AG
Questions?
Alexander (Sascha) Klein
codecentric AGCuriestr. 270563 Stuttgart
tel +49 (0) 711.674 00 - 328fax +49 (0) 172.529 40 [email protected]
@saschaklein
www.codecentric.de
blog.codecentric.de
03.06.14 50