Operating
Microservices
with
Andrés Viedma
@andres_viedma
Andrés ViedmaAndrés Viedma
@andres_viedma@andres_viedma
Andrés ViedmaAndrés Viedma
@andres_viedma@andres_viedma
curl 
-u "jsonrpc:19ffd9709d03ce50675c3a43d1c49c1ac207f4bc45f06c5b2701fbdf8929" 
-d '{"jsonrpc": "2.0", "id": 1, 
"method": "getAllTasks", "params": , {"project_id": 1, "status_id": 1} }' 
http://demo.kanboard.net/jsonrpc.php
01 LET'S PLAY!
(IN THE MUD)
Our weapon: the Groovy shell groovysh
Our basic Groovy Client
class JsonRpcClient {
(...)
def makeCall(String method, Map params = [:]) {
try {
(... json call ...)
return json.result
} catch (HttpResponseException e) {
(...)
}
}
}
class JsonRpcClient {
def methodMissing(String name, args) {
return makeCall(name, args)
}
def makeCall(String method, Map params = [:]) {
(...)
}
(...)
}
Our dynamic Groovy Client
class JsonRpcClient {
def methodMissing(String name, args) {
return makeCall(name, args)
}
def makeCall(String method, Map params = [:]) {
(...)
}
(...)
}
groovy:000> client = new JsonRpcClient(...)
===> jsonrpc.JsonRpcClient@a0a9fa5
groovy:000> client.getAllTasks(project_id: 1, status_id: 2)
Our dynamic Groovy Client
Our dynamic REST Groovy Client
blog.posts.”37”.comments()
blog.posts.”37”.delete()
blog.posts.”37” = [text: 'xxx', ...]
GET
POST
PUT
DELETE
blog.posts.”37”.comments << [text: 'xxx', …]
blog.posts.”37”.comments.add(text: 'xxx', …)
class RestGroovyClient extends RestGroovyClientPath {
String base
(...)
private doGet(String path, params = [:]) { … }
private doPut(String path, params = [:]) { … }
private doPost(String path, params = [:]) { … }
private doDelete(String path, params = [:]) { … }
}
class RestGroovyClientPath {
RestGroovyClient client = null
String path = ''
(...)
}
Our dynamic REST Groovy Client
class RestGroovyClientPath {
def propertyMissing(String name) {
newPath(name)
}
def getAt(name) {
newPath(name.toString())
}
private RestGroovyClientPath newPath(String name) {
return new RestGroovyClientPath(
client: client, path: nextpath(name))
}
(...)
}
Our dynamic REST Groovy Client
blog.posts.”37”.comments()
blog['posts'][37].comments()
class RestGroovyClientPath {
def methodMissing(String name, args) {
return client.doGet(nextpath(name), args)
}
def propertyMissing(String name, value) {
return client.doPut(nextpath(name), args)
}
def leftShift(value) {
return client.doPost(path, value)
}
def delete() {
return client.doDelete(path)
}
}
Our dynamic REST Groovy Client
blog.posts.”37”.comments()
blog.posts.”37” = [...]
blog.posts << [...]
blog.posts.”37”.delete()
02 YOUR LITTLE
BLACK BOOK
What if we create a directory of our services?
Organized as a tree
Grouped by features, by environment...
Register as shell variables
The ConfigSlurper
import dynapiclient.rest.*
jsonrpc {
kanboard = new JsonRpcClient(
base: 'http://demo.kanboard.net/jsonrpc.php',
clientHandler: { it.auth.basic 'demo', 'demo123' } )
}
rest {
(...)
marvel = new RestDynClient(
base: 'http://gateway.marvel.com/', path: '/v1/public',
paramsHandler: this.&marvelAuthenticate)
}
void marvelAuthenticate(Map callParams, String method) {
(...)
}
jsonrpc.kanboard.getMyProjects()
The ConfigSlurper
import dynapiclient.rest.*
jsonrpc {
kanboard = new JsonRpcClient(
base: 'http://demo.kanboard.net/jsonrpc.php',
clientHandler: { it.auth.basic 'demo', 'demo123' } )
}
rest {
(...)
marvel = new RestDynClient(
base: 'http://gateway.marvel.com/', path: '/v1/public',
paramsHandler: this.&marvelAuthenticate)
}
void marvelAuthenticate(Map callParams, String method) {
(...)
}
Available on startup: ~/.groovy/groovysh.profile
groovyUserHome = new File(System.getProperty('user.home'), '.groovy')
file = new File(groovyUserHome, 'assets.groovy')
binding.variables <<
new ConfigSlurper().parse(file.toURI().toURL())
Teamwork: share the directory
@Grab(group='org.springframework',
module='spring-orm',
version='3.2.5.RELEASE')
Teamwork: share the directory
Fast release cycle
@Grab(group='org.springframework',
module='spring-orm',
version='3.2.5.RELEASE')
@SourceGrab(
'https://github.com/andresviedma/
groovy-assets-directory-example.git')
AST transformation
Source Grapes Available in
Classpath
@SourceGrab('<url>')
public class myscript extends Script {
static {
SourceGrape.grab("<url>");
}
(...)
}
TRICK Add the sources directory to the classpath
in runtime and... it just works!
Merge directory parts (ConfigSlurper)
1. Passwords (local)
2. The main directory (git)
3. Personal / dev assets (local)
TRICK Merge config files: append all the files
and then parse them
03 I NEVER FORGET
A FACE
(BUT IN YOUR CASE, I'LL MAKE
AN EXCEPTION)
Asking for help
marvel.characters(doc)
groovy:000> rest.marvel.characters
===> **** /v1/public/characters
** GET: Fetches lists of characters.
Params: name, nameStartsWith, modifiedSince, comics, series, events,
stories, orderBy, limit, offset
Next: {characterId}
groovy:000>
marvel.characters()GET
marvel.characters.help()(doc)
Asking for help
groovy:000> rest.marvel.characters."1010354"
===> **** /v1/public/characters/{characterId}
** GET: Fetches a single character by id.
[characterId(*)]
Next: comics, events, series, stories
groovy:000>
Replaceable URL path sections
What about Autocomplete?
We have the ExpandoMetaClass!
groovy:000> x = 2
===> 2
groovy:000> x.metaClass.sayHello = { args -> println 'hello' }
===> groovysh_evaluate$_run_closure1@57adfab0
groovy:000> x.sayHello()
hello
===> null
groovy:000> x.
abs() byteValue() compareTo( doubleValue() downto(
floatValue()
intValue() longValue() power( shortValue() times(
upto(
groovy:000>
What about Autocomplete?
We have the ExpandoMetaClass!
groovy:000> x = 2
===> 2
groovy:000> x.metaClass.sayHello = { args -> println 'hello' }
===> groovysh_evaluate$_run_closure1@57adfab0
groovy:000> x.sayHello()
hello
===> null
groovy:000> x.
abs() byteValue() compareTo( doubleValue() downto(
floatValue()
intValue() longValue() power( shortValue() times(
upto(
groovy:000>
What about Autocomplete?
The shell wraps the Expando In a HandleMetaClass
groovy:000> x.metaClass
===> HandleMetaClass[MetaClassImpl[class java.lang.String]]
groovy:000> x.metaClass."hello" = { name -> "Hello ${name}" }
===> groovysh_evaluate$_run_closure1@feba70e
groovy:000> x.metaClass
===> HandleMetaClass[ExpandoMetaClass[class java.lang.String]]
groovy:000> x.metaClass.getMetaMethods()*.name.findAll { it.startsWith('h') }
===> [hasProperty]
TRICK Use InvokerHelper.getMetaClass(x)
instead of x.metaClass
class AutocompleteMetaClass extends DelegatingMetaClass {
static void addFakeMethodsToObject(Object object, methods, properties) {
def autocomplete = configureMetaClass(object)
def innerMeta = autocomplete.originalMetaClass
addFakeMethodsToExpando(innerMeta, object, methods, properties)
}
private static MetaClass configureMetaClass(Object object) {
def metaOld = InvokerHelper.getMetaClass(object)
if (metaOld.getClass().name != AutocompleteMetaClass.class.name) {
object.metaClass = new AutocompleteMetaClass(metaOld)
}
return InvokerHelper.getMetaClass(object)
}
(...)
}
Create your own brand AutocompleteMetaClass
class AutocompleteMetaClass extends DelegatingMetaClass {
(...)
final MetaClass expando
AutocompleteMetaClass(MetaClass originalMetaClass) {
super(originalMetaClass)
this.expando = originalMetaClass
}
List<MetaMethod> getMetaMethods() {
return expando.getExpandoMethods()
}
List<MetaBeanProperty> getProperties() {
return expando.getExpandoProperties()
}
}
Create your own brand AutocompleteMetaClass
Let's demo!!!
04 So...?
(JUST ENDING, I PROMISE...)
Give me the code!!!
https://github.com/andresviedma/sourcegrape
https://github.com/andresviedma/dynapiclient-groovy
https://github.com/andresviedma/groovy-assets-directory-example
Where did all this led us?
Shared microservices directory
Dynamic service calls
Autocomplete
Integrated help
A powerful shell
But, in the way there, mostly...
Learning about Groovy scripting black magic
Having fun!
Andrés ViedmaAndrés Viedma
@andres_viedma@andres_viedma
Questions?

Operating Microservices with Groovy

  • 1.
  • 2.
  • 3.
  • 5.
    curl -u "jsonrpc:19ffd9709d03ce50675c3a43d1c49c1ac207f4bc45f06c5b2701fbdf8929" -d '{"jsonrpc": "2.0", "id": 1, "method": "getAllTasks", "params": , {"project_id": 1, "status_id": 1} }' http://demo.kanboard.net/jsonrpc.php
  • 6.
  • 7.
    Our weapon: theGroovy shell groovysh
  • 8.
    Our basic GroovyClient class JsonRpcClient { (...) def makeCall(String method, Map params = [:]) { try { (... json call ...) return json.result } catch (HttpResponseException e) { (...) } } }
  • 9.
    class JsonRpcClient { defmethodMissing(String name, args) { return makeCall(name, args) } def makeCall(String method, Map params = [:]) { (...) } (...) } Our dynamic Groovy Client
  • 10.
    class JsonRpcClient { defmethodMissing(String name, args) { return makeCall(name, args) } def makeCall(String method, Map params = [:]) { (...) } (...) } groovy:000> client = new JsonRpcClient(...) ===> jsonrpc.JsonRpcClient@a0a9fa5 groovy:000> client.getAllTasks(project_id: 1, status_id: 2) Our dynamic Groovy Client
  • 11.
    Our dynamic RESTGroovy Client blog.posts.”37”.comments() blog.posts.”37”.delete() blog.posts.”37” = [text: 'xxx', ...] GET POST PUT DELETE blog.posts.”37”.comments << [text: 'xxx', …] blog.posts.”37”.comments.add(text: 'xxx', …)
  • 12.
    class RestGroovyClient extendsRestGroovyClientPath { String base (...) private doGet(String path, params = [:]) { … } private doPut(String path, params = [:]) { … } private doPost(String path, params = [:]) { … } private doDelete(String path, params = [:]) { … } } class RestGroovyClientPath { RestGroovyClient client = null String path = '' (...) } Our dynamic REST Groovy Client
  • 13.
    class RestGroovyClientPath { defpropertyMissing(String name) { newPath(name) } def getAt(name) { newPath(name.toString()) } private RestGroovyClientPath newPath(String name) { return new RestGroovyClientPath( client: client, path: nextpath(name)) } (...) } Our dynamic REST Groovy Client blog.posts.”37”.comments() blog['posts'][37].comments()
  • 14.
    class RestGroovyClientPath { defmethodMissing(String name, args) { return client.doGet(nextpath(name), args) } def propertyMissing(String name, value) { return client.doPut(nextpath(name), args) } def leftShift(value) { return client.doPost(path, value) } def delete() { return client.doDelete(path) } } Our dynamic REST Groovy Client blog.posts.”37”.comments() blog.posts.”37” = [...] blog.posts << [...] blog.posts.”37”.delete()
  • 15.
  • 17.
    What if wecreate a directory of our services? Organized as a tree Grouped by features, by environment... Register as shell variables
  • 18.
    The ConfigSlurper import dynapiclient.rest.* jsonrpc{ kanboard = new JsonRpcClient( base: 'http://demo.kanboard.net/jsonrpc.php', clientHandler: { it.auth.basic 'demo', 'demo123' } ) } rest { (...) marvel = new RestDynClient( base: 'http://gateway.marvel.com/', path: '/v1/public', paramsHandler: this.&marvelAuthenticate) } void marvelAuthenticate(Map callParams, String method) { (...) } jsonrpc.kanboard.getMyProjects()
  • 19.
    The ConfigSlurper import dynapiclient.rest.* jsonrpc{ kanboard = new JsonRpcClient( base: 'http://demo.kanboard.net/jsonrpc.php', clientHandler: { it.auth.basic 'demo', 'demo123' } ) } rest { (...) marvel = new RestDynClient( base: 'http://gateway.marvel.com/', path: '/v1/public', paramsHandler: this.&marvelAuthenticate) } void marvelAuthenticate(Map callParams, String method) { (...) }
  • 20.
    Available on startup:~/.groovy/groovysh.profile groovyUserHome = new File(System.getProperty('user.home'), '.groovy') file = new File(groovyUserHome, 'assets.groovy') binding.variables << new ConfigSlurper().parse(file.toURI().toURL())
  • 21.
    Teamwork: share thedirectory @Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE')
  • 22.
    Teamwork: share thedirectory Fast release cycle @Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE') @SourceGrab( 'https://github.com/andresviedma/ groovy-assets-directory-example.git')
  • 23.
    AST transformation Source GrapesAvailable in Classpath @SourceGrab('<url>') public class myscript extends Script { static { SourceGrape.grab("<url>"); } (...) } TRICK Add the sources directory to the classpath in runtime and... it just works!
  • 24.
    Merge directory parts(ConfigSlurper) 1. Passwords (local) 2. The main directory (git) 3. Personal / dev assets (local) TRICK Merge config files: append all the files and then parse them
  • 25.
    03 I NEVERFORGET A FACE (BUT IN YOUR CASE, I'LL MAKE AN EXCEPTION)
  • 28.
    Asking for help marvel.characters(doc) groovy:000>rest.marvel.characters ===> **** /v1/public/characters ** GET: Fetches lists of characters. Params: name, nameStartsWith, modifiedSince, comics, series, events, stories, orderBy, limit, offset Next: {characterId} groovy:000> marvel.characters()GET marvel.characters.help()(doc)
  • 29.
    Asking for help groovy:000>rest.marvel.characters."1010354" ===> **** /v1/public/characters/{characterId} ** GET: Fetches a single character by id. [characterId(*)] Next: comics, events, series, stories groovy:000> Replaceable URL path sections
  • 30.
    What about Autocomplete? Wehave the ExpandoMetaClass! groovy:000> x = 2 ===> 2 groovy:000> x.metaClass.sayHello = { args -> println 'hello' } ===> groovysh_evaluate$_run_closure1@57adfab0 groovy:000> x.sayHello() hello ===> null groovy:000> x. abs() byteValue() compareTo( doubleValue() downto( floatValue() intValue() longValue() power( shortValue() times( upto( groovy:000>
  • 31.
    What about Autocomplete? Wehave the ExpandoMetaClass! groovy:000> x = 2 ===> 2 groovy:000> x.metaClass.sayHello = { args -> println 'hello' } ===> groovysh_evaluate$_run_closure1@57adfab0 groovy:000> x.sayHello() hello ===> null groovy:000> x. abs() byteValue() compareTo( doubleValue() downto( floatValue() intValue() longValue() power( shortValue() times( upto( groovy:000>
  • 32.
    What about Autocomplete? Theshell wraps the Expando In a HandleMetaClass groovy:000> x.metaClass ===> HandleMetaClass[MetaClassImpl[class java.lang.String]] groovy:000> x.metaClass."hello" = { name -> "Hello ${name}" } ===> groovysh_evaluate$_run_closure1@feba70e groovy:000> x.metaClass ===> HandleMetaClass[ExpandoMetaClass[class java.lang.String]] groovy:000> x.metaClass.getMetaMethods()*.name.findAll { it.startsWith('h') } ===> [hasProperty] TRICK Use InvokerHelper.getMetaClass(x) instead of x.metaClass
  • 33.
    class AutocompleteMetaClass extendsDelegatingMetaClass { static void addFakeMethodsToObject(Object object, methods, properties) { def autocomplete = configureMetaClass(object) def innerMeta = autocomplete.originalMetaClass addFakeMethodsToExpando(innerMeta, object, methods, properties) } private static MetaClass configureMetaClass(Object object) { def metaOld = InvokerHelper.getMetaClass(object) if (metaOld.getClass().name != AutocompleteMetaClass.class.name) { object.metaClass = new AutocompleteMetaClass(metaOld) } return InvokerHelper.getMetaClass(object) } (...) } Create your own brand AutocompleteMetaClass
  • 34.
    class AutocompleteMetaClass extendsDelegatingMetaClass { (...) final MetaClass expando AutocompleteMetaClass(MetaClass originalMetaClass) { super(originalMetaClass) this.expando = originalMetaClass } List<MetaMethod> getMetaMethods() { return expando.getExpandoMethods() } List<MetaBeanProperty> getProperties() { return expando.getExpandoProperties() } } Create your own brand AutocompleteMetaClass
  • 35.
  • 36.
  • 37.
    Give me thecode!!! https://github.com/andresviedma/sourcegrape https://github.com/andresviedma/dynapiclient-groovy https://github.com/andresviedma/groovy-assets-directory-example
  • 38.
    Where did allthis led us? Shared microservices directory Dynamic service calls Autocomplete Integrated help A powerful shell
  • 39.
    But, in theway there, mostly... Learning about Groovy scripting black magic Having fun! Andrés ViedmaAndrés Viedma @andres_viedma@andres_viedma Questions?