Slide 1

Slide 1 text

BUILDING
 MICRO-SERVICES Without Reinventing The Wheel

Slide 2

Slide 2 text

Ivan Vanderbyl @ivanderbyl github.com/ivanvanderbyl I’m Ivan Vanderbyl, I’m one of the front-end engineers at DigitalOcean, where we’re building a simpler cloud for developers.

Slide 3

Slide 3 text

MICRO-SERVICE? So tonight I want to talk about micro services, and the tools we extracted from our own services, and our move towards breaking our monolithic rails applications in to smaller services. So what is a micro service?

Slide 4

Slide 4 text

…the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API… http://martinfowler.com/articles/microservices.html — Martin Fowler Martin Fowler has an awesome article on some highly detailed descriptions of what is and isn’t a microservice. But essentially it’s a process which handles a singular defined piece of work, which another process sends to it over a communication channel, usually HTTP, upon which it does some work and returns a result.

Slide 5

Slide 5 text

Building blocks of a Micro-service Microservice 1 Core Service / Application Public API API Client Domain Logic Domain Services HTTP These are the building blocks of a microservice. We have a standalone process which another process can call to process some piece of data and handle the response. In our case this is over HTTP, on an internal network.

Slide 6

Slide 6 text

The Wheels Everyone reinvents these Public API API Client I won’t focus on the architecture of the service itself, rather the communication components. The wheels, if you will. These are the most hotly contacted components and nearly always get reinvented.

Slide 7

Slide 7 text

Core Application Internals of an API client External Microservice API Client Resolver Response Extraction Data Model Domain Logic Request Serialisation HTTP Interface When we take a look at the internals of the API client component, we see some common patterns, something to talk raw HTTP, something to resolve the requests for specific resources, and something to turn an object in to JSON or some other format.

Slide 8

Slide 8 text

Microservice 1 Internals of an API Server External Microservice API Server Router Response Extraction Data Model Domain Logic Request Serialisation HTTP Interface The internals of an API server are pretty much the same. You might not be handling the extraction yourself, but you’ll more than likely be handling the serialisation.

Slide 9

Slide 9 text

Router / Resolver Response Extraction Data Model Request Serialisation HTTP Interface ActiveModelSerializers, JBuilder, OAT , rabl, to_json ActionPack, Sinatra ActiveResource Faraday, Net::HTTP, Rack So across the client and server we have a mix of common tools which pretty much everyone ties together. If you’re using Rails most of this can be handled out of the box, but that’s not the case with Sinatra, for example. We were using ActiveModelSerializers, but we had to make a lot of internal modifications to support the responses we wanted to produce.

Slide 10

Slide 10 text

New Building Blocks Router / Resolver Response Extraction Data Model Request Serialisation HTTP Interface Kartograph ResourceKit Kartograph Faraday So across the client and server we have a mix of common tools which pretty much everyone ties together. If you’re using Rails most of this can be handled out of the box, but that’s not the case with Sinatra, for example.

Slide 11

Slide 11 text

{ kartograph } https://github.com/digitalocean/kartograph We built kartograph kind of by chance when I was pairing with bobby tables on the west coast for a few days.

Slide 12

Slide 12 text

Object JSON Object Data Serialiser and Extractor { kartograph } It does exactly two things: Turns an object in to JSON, and converts JSON back to that Object.

Slide 13

Slide 13 text

class DropletMapping include Kartograph::DSL kartograph do root_key plural: 'droplets', singular: 'droplet', scopes: [:read] property :id, scopes: [:read] property :name, scopes: [:read, :create] end end lib/mappings/droplet_mapping.rb https://github.com/digitalocean/droplet_kit/blob/master/lib/droplet_kit/mappings/droplet_mapping.rb Kartograph does this using Mappings. Here’s an example of a really simple mapping, taken from DropletKit, our official API client for the DigitalOcean API.

Slide 14

Slide 14 text

class DropletMapping include Kartograph::DSL kartograph do root_key plural: 'droplets', singular: 'droplet', scopes: [:read] property :id, scopes: [:read] property :name, scopes: [:read, :create] end end lib/mappings/droplet_mapping.rb https://github.com/digitalocean/droplet_kit/blob/master/lib/droplet_kit/mappings/droplet_mapping.rb A kartograph mapping is simply a ruby class composed of the Kartograph DSL

Slide 15

Slide 15 text

class DropletMapping include Kartograph::DSL kartograph do root_key plural: 'droplets', singular: 'droplet', scopes: [:read] property :id, scopes: [:read] property :name, scopes: [:read, :create] end end lib/mappings/droplet_mapping.rb https://github.com/digitalocean/droplet_kit/blob/master/lib/droplet_kit/mappings/droplet_mapping.rb In this example we’re specifying two representations of our object. One for read and one for create. So you can see we only produce a root element on read, and not on create. We also don’t include the id attribute when serialising for create.

Slide 16

Slide 16 text

droplet = double("Droplet", name: "Sammy", id: 1) DropletMapping.representation_for(:create, droplet) # => {"name": "Sammy"} DropletMapping.representation_for(:read, droplet) # => {"droplet": {"name": "Sammy", "id": 1}} Serialising an object We can call `representation_for` and produce a representation for this object based on the given scope. So we get a root-less object for making requests, and an object with a root for responding to the client. So one of these would be used in the client, and one on the server.

Slide 17

Slide 17 text

Serialising a collection DropletMapping.represent_collection_for(:read, [droplet]) # => {"droplets": [{"name": "Sammy", "id": 1}]} Similarly we can also represent a collection of objects very easily, and get a pluralization of the root key name.

Slide 18

Slide 18 text

Extraction droplet_json = '{"droplet": {"name": "Sammy", "id": 1}}' DropletMapping.extract_single(droplet_json, :read) # => Droplet droplets_json = '{"droplets": [{"name": "Sammy", "id": 1}]}' DropletMapping.extract_collection(droplets_json, :read) # => [Droplet] Deserialisation, or Extraction as we call it, works exactly the same. We pass in some json, and receive an object according to our mapping and scope, in this case a Droplet object.

Slide 19

Slide 19 text

No Dependencies Caching Rails Cache. Custom Expiration. Supports complicated nested data structures Production Ready™ Powers DigitalOcean API V2, DropletKit, and internal clients for DigitalOcean DNS. { kartograph } digitalocean/kartograph And that’s kartograph. It has no dependencies, supports caching with custom expiration, handles complicated data structures, and it’s used in production.

Slide 20

Slide 20 text

gem "kartograph" Throw this in your Gemfile and bundle it.

Slide 21

Slide 21 text

ResourceKit https://github.com/digitalocean/resource_kit So that’s the basics of Kartograph. Let’s talk about ResourceKit, this was another extraction we pulled from DropletKit.

Slide 22

Slide 22 text

Provides the “How” of talking to remote APIs ResourceKit is essentially some very lightweight tools for building API clients. It provides the tools to describe how we make calls and handle responses.

Slide 23

Slide 23 text

Doesn’t actually make API calls It doesn’t make API calls itself, instead it will talk to any HTTP library you provide which responds to http verbs like get and post. Faraday works well here. You can also give it a test double.

Slide 24

Slide 24 text

Provides Test Helpers expect(MyResourceClass).to have_action(:delete). that_handles(:no_content). at_path('/users') ResourceKit also provides test helpers for RSpec to make testing your API clients much easier. I’ll show you an example shortly.

Slide 25

Slide 25 text

class DropletResource < ResourceKit::Resource resources do action :find do verb :get # get is assumed if this is omitted path '/droplets/:id' handler(200) { |response| DropletMapping.extract_single(response.body, :read) } end action :all, 'GET /droplets' do handler(200) { |body| DropletMapping.extract_collection(body, :read) } end end end lib/resources/droplet_resource.rb Here’s how we would make a resource which talks to our Droplets API endpoint. We define two actions, all and find.

Slide 26

Slide 26 text

lib/resources/droplet_resource.rb action :create, 'POST /droplets' do body { |object| DropletMapping.representation_for(:create, object) } handler(201) { |response| DropletMapping.extract_single(response.body, :read) } end We also make POST like resources easy to compose by providing a body and giving you the ability to transform it on the fly in to JSON.

Slide 27

Slide 27 text

Step 1. Provide a Connection conn = Faraday.new(url: 'http://api.digitalocean.com') do |req| req.adapter :net_http end resource = DropletResource.new(connection: conn) My favourite part, using it. So we just defined an API client for talking to the droplets endpoint, let’s use it.

Slide 28

Slide 28 text

Step 2. Use It. conn = Faraday.new(url: 'http://api.digitalocean.com') do |req| req.adapter :net_http end resource = DropletResource.new(connection: conn) all_droplets = resource.all single_droplet = resource.find(id: 123) create = resource.create(Droplet.new) The we can directly call those actions we defined earlier as methods, and behind the scenes everything is handled for us.

Slide 29

Slide 29 text

Step 3. Test It. require 'resource_kit/testing' # Tag the spec with resource_kit to bring in the helpers RSpec.describe DropletResource, resource_kit: true do it 'handles a 201 with response body' do expect(described_class).to handle_response(:create). with(status: 201, body: ‘{“droplets”:[{…}]}’) do |handled| expect(handled).to all(be_kind_of(Droplet)) end end end https://github.com/digitalocean/resource_kit/tree/master/lib/resource_kit/testing And finally the third step is to test it. We provide some easy to use helpers for declaratively writing specs.

Slide 30

Slide 30 text

BYO Data Model Bring your own data model, in DropletKit we use simple classes, you can use anything you want which has a setter and getter method.

Slide 31

Slide 31 text

gem "resource_kit" And again throw this one in your Gemfile and bundle it.

Slide 32

Slide 32 text

No Dependencies (Except Faraday) Batteries Included Use what you need Production Ready™ Powers DropletKit ResourceKit digitalocean/resource_kit ResourceKit only depends on Faraday, I’m not 100% sure it needs to, so if that becomes a problem I’m sure we could remove it. You don’t need to use all of ResourceKit, only what you need.

Slide 33

Slide 33 text

Real world example DropletKit digitalocean/droplet_kit For a complete and advanced example, check out DropletKit on the Githubs.

Slide 34

Slide 34 text

Ivan Vanderbyl @ivanderbyl github.com/ivanvanderbyl We’re Hiring careers.digitalocean.com Thanks Thanks, we are always, so if you fancy a trip to NYC, come and talk to me.