operating microservices with groovy
TRANSCRIPT
OperatingMicroservices
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 LITTLEBLACK 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, offsetNext: {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===> 2groovy:000> x.metaClass.sayHello = { args -> println 'hello' }===> groovysh_evaluate$_run_closure1@57adfab0groovy:000> x.sayHello()hello===> nullgroovy: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===> 2groovy:000> x.metaClass.sayHello = { args -> println 'hello' }===> groovysh_evaluate$_run_closure1@57adfab0groovy:000> x.sayHello()hello===> nullgroovy: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@feba70egroovy: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?