Ruby SOA: Less Pain and More Automation, Please!

A Service-Oriented Architecture can be an enormous pain to get right. You can read about Rails folks cutting apart monolithic apps into services… Painfully.

I believe there’s a different approach that can help a lot, and that most modern frameworks will soon be using it. I built a framework that does it for OnLive, and I wish I could say it was an all-original idea. The Rails guys are clearly headed the same direction… Learn it now, avoid the rush!

The gist: you can combine multiple services into a single process in Ruby, while making calls between them seamlessly switch between local and remote. It’s not hard to do now, and it will become really, really easy when your framework supports it.

I already built a framework that supports it: the code is below!

How Does This Work, Now?

We started from Sinatra. The idea was that we could declare routes and the call protocol at the same time. For instance:

require "olaf"
require "olaf/service"
require "olaf/http"

class MyService < Olaf::Service
  route_name :hello_world
  get "/helloworld" do |params|
    # Implement here, just like vanilla Sinatra
  end
end

This lets you implement an incoming “hello, world” request in Sinatra… And also to generate a call to it with the name “hello_world”. You can automatically generate a client and make the call trivially:

TestService.client.hello_world

The fun comes when you realize that you can run the service locally or remote… And the client will call locally for local, or as HTTP for remote.

You can divide your app up into services, but start and stop them all together in one process. You can run single-threaded without waiting for HTTP responses to come back — so it works much better than if you just build a bunch of Rack apps and tied them together with a single config.ru file.

It’s just a method call locally, but it’s a full remote HTTP call when you run as multiple processes. You do have to declare where it’s running if it’s remote, of course.

Better yet, you can specify a lot more about each call than just a name. Here’s an excerpt from one of our Olaf-based services:

module OLApplication
  class ApplicationService < Olaf::Service
    service_name "Application"
    default_content_type :json

    # Swagger path prefix descriptions
    api_path "/", :desc => "Operations about Applications"
    api_path "/:application_id", :desc => "Operations about Specific Applications"

    # Here's all the specifics about the next route
    route_name :list_applications
    desc "Get a list of applications"
    param :name, String, "Application Name"
    param :limit, Fixnum, "Number of applications per page returned"
    errors 400 => "Invalid request parameters",
           503 => "Internal server problem"
    return_type [ DomainObjects::Application ]

    # And here's the route itself -- just standard Sinatra
    get "/" do
      apps = DomainObjects::Application.list_applications(params)

      return_value apps
    end

    # More routes go here...
  end
end

The call can verify parameters, even locally. And errors can be converted to error objects or to raise exceptions, at your option. Just use a bang method when you want an exception on error.

Does It Work Well?

We used this exact code for a large project at OnLive. The project is dead now — a lot of prototype projects just tell you that you’re going the wrong direction — you should abandon them when that becomes clear. Fail quickly and cheaply.

Olaf worked pretty well. I’ll definitely do the same thing again with a few tweaks.

We used a dual testing setup — running all nine (!) services together locally in a few tests, and running as nine separate Sinatra services in others. That makes sure nobody breaks the nice monolithic/SOA duality you have going, in either direction. I’ll definitely have the dual testing setup again next time.

Of course, every prototype has flaws. Deployment can be harder with nine services, and you have to figure out — will we deploy monolithically? Entirely separate? With four sevices in one process and five in another? That can complicate process management (e.g. God, Monit, Bluepill, Daemontools) and other aspects of deployment (e.g. Capistrano, Chef).

SOA usually means harder process management and harder deployment, and Olaf doesn’t do much to make it easy. It’s possible that we can do that better… But our first attempt didn’t.

Side Benefits

One of the cool things about declaring a full interface right in the Sinatra file is that you can generate other bindings, not just Ruby service calls. In fact, you can think of it as combining a callout interface (like Swig, Swagger, etc.) directly with your Sinata file. You can actually generate all sorts of clients — if you wanted a Java wrapper for your HTTP client, for instance, everything you need for that is already built right in.

We used it to attach our servers to Swagger, which was great for testing. We didn’t fully automate generating Swagger, but we got mostly there — and the remaining bits aren’t hard.

We also got to unify a lot of our models with our controller code. The model knows how to render itself in JSON, and the controller can validate against the models. This is going to be a complicated area to get right, but we got a nice start on it. You can roll your API contract right into your models, and have them verified at the service call site.

The Code… And Where To Go Next

You may have seen a link or two to our GitHub repo with this code — feel free to look through. You can adapt all of this to plain-vanilla Sinatra pretty easily. You could especially focus on declaring the service’s requests and the Ruby service client, both local and HTTP.

One thing — don’t use Olaf directly. Olaf is a proof of concept. I think it’s a good one, but you shouldn’t use it verbatim. Instead, adapt the idea of the ServiceClient to your own favorite framework, whether that’s Sinatra, Rails or something else. Our implementation is under MIT License — steal liberally.

I’ll be doing the same thing in my next big Ruby service framework — taking the parts of Olaf that work well, and leaving the rest.

And perhaps I’ll solve those deployment problems… I have some fun ideas!

I hope you do too.

So what do you think?

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: