Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Operating Microservices with Groovy

1,564 views

Published on

When you are developing a microservices arquitecture, you often have to call some of them, for manual testing, scripting and/or operations. Given the dynamic nature of Groovy, could it be used to make easier these calls?

* Could we call our services with a syntax shorter and more natural, instead of using long curls, but not losing the history capability of a shell?
* Could we query the documentation of the operations in the same way?
* Could we even have autocomplete features of our service operations, so that we don’t have to remember their names?
* Could we do all this dynamically, without having to create a specific client for each service, but calling it as if it were?
* Could we have a directory of available services, so that we don’t have to remember them?
* Could this directory and the utility classes be updated automatically for all the team when someone makes a change, using a Git repository?

You are invited to a journey through the mud, exploring features such as Groovy metaclasses, class loading, REST services, shell capabilities and AST transformations, and making some dark tricks to achieve what we want.

Repositories with the presentation code:
https://github.com/andresviedma/groovy-assets-directory-example
https://github.com/andresviedma/dynapiclient-groovy
https://github.com/andresviedma/sourcegrape

[Presented at Greach 2016]

Published in: Software
  • Be the first to comment

Operating Microservices with Groovy

  1. 1. Operating Microservices with Andrés Viedma @andres_viedma
  2. 2. Andrés ViedmaAndrés Viedma @andres_viedma@andres_viedma
  3. 3. Andrés ViedmaAndrés Viedma @andres_viedma@andres_viedma
  4. 4. 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
  5. 5. 01 LET'S PLAY! (IN THE MUD)
  6. 6. Our weapon: the Groovy shell groovysh
  7. 7. Our basic Groovy Client class JsonRpcClient { (...) def makeCall(String method, Map params = [:]) { try { (... json call ...) return json.result } catch (HttpResponseException e) { (...) } } }
  8. 8. class JsonRpcClient { def methodMissing(String name, args) { return makeCall(name, args) } def makeCall(String method, Map params = [:]) { (...) } (...) } Our dynamic Groovy Client
  9. 9. 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
  10. 10. 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', …)
  11. 11. 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
  12. 12. 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()
  13. 13. 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()
  14. 14. 02 YOUR LITTLE BLACK BOOK
  15. 15. What if we create a directory of our services? Organized as a tree Grouped by features, by environment... Register as shell variables
  16. 16. 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()
  17. 17. 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) { (...) }
  18. 18. 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())
  19. 19. Teamwork: share the directory @Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE')
  20. 20. 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')
  21. 21. 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!
  22. 22. 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
  23. 23. 03 I NEVER FORGET A FACE (BUT IN YOUR CASE, I'LL MAKE AN EXCEPTION)
  24. 24. 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)
  25. 25. 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
  26. 26. 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>
  27. 27. 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>
  28. 28. 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
  29. 29. 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
  30. 30. 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
  31. 31. Let's demo!!!
  32. 32. 04 So...? (JUST ENDING, I PROMISE...)
  33. 33. Give me the code!!! https://github.com/andresviedma/sourcegrape https://github.com/andresviedma/dynapiclient-groovy https://github.com/andresviedma/groovy-assets-directory-example
  34. 34. Where did all this led us? Shared microservices directory Dynamic service calls Autocomplete Integrated help A powerful shell
  35. 35. But, in the way there, mostly... Learning about Groovy scripting black magic Having fun! Andrés ViedmaAndrés Viedma @andres_viedma@andres_viedma Questions?

×