Jeff Beck
TechLead at ReachLocal
## What I mean by MicroService
* Single concern
* Deployable in isolation
Over the course of acquisitions and expanding the products we have come out with a polyglot architecture including Java, Groovy, Ruby, PHP, Node, and PERL. In order to take advantage of our existing talent and software, we have started down the micro-service path.
## Where Grails Fits
* REST Resources
* GORM
~~
## Where Grails Doesn't Fit
* Batch Jobs
* Lacking any web endpoint
Acts as the source of truth about subscriptions.
- Exposes the Subscription REST Resource
- Emanates Events
~~
- Application Monitoring
- Security
- Server Setup
- Builds
- Deployments
## Building our Service
All our Grails micro services start out the same way.
~~
### Project Setup
* Grails create-app
* Change Java Version to 1.7
* Standard Forked Mode Settings
* Add Internal Nexus
~~
### Plugin Setup
* Add our normal plugins
* Remove the extra standard plugins
* Set up cucumber
* Configure GORM
~~
### Build and Repo Setup
* Commit everything as the starting point
* Add the new CI job
* Bring up a dev server
We add scenarios, using cucumber allows us to keep our testing more DRY.
~~
Scenario: Get nonexistent subscription by ID
Given I am a valid api client
And A valid subscription ID which does not match any subscription
When I request a subscription by ID
Then I get a 404 response
~~
Scenario: Get existing subscription by ID
Given I am a valid api client
And A valid subscription ID which matches a subscription
When I request a subscription by ID
Then I get a 200 response
~~
Given(~'^A valid subscription ID which matches a subscription$') { ->
//Have a valid subscription which exposes an ID
}
Given(~'^A valid subscription ID which does not match any subscription$') { ->
//Have an ID which is valid but no matching subscription
}
When(~'^I request a subscription by ID$') { ->
//Do actual request
}
Then(~'^I get a (\\d+) response$') { int statusCode ->
if (response.statusCode != statusCode) {
println response.asString()
}
assert response.statusCode == statusCode
}
## What we reused
* Health Checks
* Security
* Server Setup
* Deployments
* SI Components
To support reusable monitoring we expose a health check in a known way that attempts to be both human readable and programmatically useful.
~~
Status Code 200
{
"dependencies": {
"database":"OK",
"file-access":"WARN"
}
}
~~
Via a Grails plugin we share
- Common healthcheck implementations
- A registry of healthchecks
- Controller that supports the expected output
## Security
We do server to server authentication with a token. So checking the `Authorization` header the plugin authenticates a client.
~~
### Our Plugin
* Expects a known GORM object that has an ID which is the token.
* Uses a static list of resoure names to secure
* Intended to be as light weight as possible
~~
### Experiences with our plugin
* Moving away from our custom implementation towards a SpringSecurity grails plugin
* Was opt in security which was easy to miss a controller
* Intently lacked roles which we have found a use-case for now
We use an internal Nexus repo, and release plugins to that.
~~
- Supports Versioning
- Dependency resolution works the way the rest of Grails does
- Changes don't reload
~~
- Tend to adding features allowing customization.
- Each plugin is a project it needs CI, CodeNarc, etc
- Use Versioning
- CI pushing out SNAPSHOT versions is very helpful
## Server Setup
We use puppet to automate our server setup. Using classes we share default setup for a Tomcat server.
~~
## init.pp
```
class apps_subscription_api (
$heap_min = '256m',
$heap_max = '1024m',
$permgen_size = '1024m'
){
class { 'standard_tomcat7_web_server':
minimum_heap => $heap_min,
maximum_heap => $heap_max,
permgen_size => $permgen_size,
}
include apps_subscription_api::config
base::nagios::hostgroup { "rsubscription_api_servers": }
base::nagios::hostparam { "_healthuri": value => '/health' }
}
```
~~
## config.pp
```
class apps_subscription_api::config {
file { '/rl/path/configs/subscription-config.groovy':
ensure => present,
owner => tomcat7,
group => tomcat7,
mode => '0400',
content => template('apps_subscription_api/subscription-config.groovy.erb'),
require => File['/rl/path/configs']
}
}
```
We automate our deployments via custom bash scripts kicked off by bamboo deployments.
~~
- Server List
- Artifact Name
- Deployment permissions
## Spring Integration Components
Our spring integration apps tend to need the same filters and transformers for our Events. Grails apps are not the only users of these components.
~~
### Shared via a JAR
* Exposes a Pojo that represents our internal idea of an Event
* De-duplication filter
* Built to include the least dependencies
As a single request can spread out across the graph of micro services it is helpful to have some way to correlating all the work back together.
~~
We use Log4J's MDC to log a correlation ID throughout the requests life in Grails.
MDC.put('correlationId', ", CorrelationId=${correlationId}")
new EnhancedPatternLayout(conversionPattern: ' %d{ISO8601}%d{ z}{GMT+0} %-5p [%t] %c{2}(:%L) - %m%X{correlationId}%n')
~~
We use Splunk for log aggregation for all applications. Allowing a query across many apps for a single correlation id.
## Centralized Monitoring
* Nagios
* NewRelic
* Starting to use dashboards of health checks
- Poor communications between teams gets worse
- Coordinating Releases
- Development to spec vs finished service
- Leaving in -SNAPSHOT dependencies
- Poor monitoring in lower than production environments
## MicroService Checklist
* Low overhead to start new projects
* Internal maven repo
* Automated deployments
* Server configuration management
* Good team to team communication
- Correct size of microservices
- Pure REST vs RESTful
- Security: Centralized vs Decentralized