Friday, November 28, 2014

Get Hooked


Have you heard about microservices yet? There's been no small amount of discussion about their benefits recently. Even Martin Fowler says that the microservices pattern of enterprise system architecture has seen many positive results among his colleagues in the past few years (read his whole article here).

If you didn't click on the link, I'll summarize it for you here: the microservice architectural style is an approach to developing a single application as a suite of small services that communicate through very lightweight mechanisms such as HTTP APIs. What does that mean? It means you break your big monolithic system down into a bunch of small parts that each do a single job and are deployed independently.

I decided to play around with a popular microservice framework based on Node.js, hook.io. It is a very interesting premise. You set up a public Gist at GitHub, which the hook.io framework executes when called. For my experiment, I created a hook which can give me the date for Easter for any given year.

The first step is to create a public Gist for your hook. You can find mine here, but it's basically just the following code:

module['exports'] = function easter(hook) {
 
  var year = hook.params.year;
  hook.debug("i am going to calculate the date of Easter for " + year);
  
  // using the "Meeus/Jones/Butcher" algorithm
  var a = year % 19;
  var b = Math.floor(year/100);
  var c = year % 100;
  var d = Math.floor(b/4);
  var e = b % 4;
  var f = Math.floor((b + 8) / 25);
  var g = Math.floor((b - f + 1) / 3);
  var h = ((19*a) + b - d - g + 15) % 30;
  var i = Math.floor (c/4);
  var k = c % 4;
  var L = (32 + (2*e) + (2*i) - h - k) % 7;
  var m = Math.floor((a + (11*h) + (22*L)) / 451);
  var month = Math.floor((h + L - (7*m) + 114) / 31);
  var day = ((h + L - (7*m) + 114) % 31) + 1;
  
  hook.debug("month is " + month);
  hook.debug("day is " + day);
  var monthNames = [ "January", "February", "March", "April", "May", "June",
    "July", "August", "September", "October", "November", "December" ];
    
  hook.res.end("Easter is " + monthNames[month-1] + " " + day + " in " + year + "\n");
 
};
 
module['exports'].schema = {
  "year": {
    "type": "number"
  }
};

On the first line, you define your module's exported function. Since every hook is passed a Hook object as its first argument, I define that as the lone parameter.

What will that Hook object look like? According to the hook.io documentation, there are a number of fields available on it. For my simple purposes, the most interesting will be Hook.debug and Hook.params. The debug field is a function that you can call to dump information out to a debug console. It is actually quite nice because all your debug ends up as an array of objects with time, data, and ip fields. But I'm getting ahead of myself...

On line 3, I use the year param to get the year that will be passed in when my hook is called (more on how that happens a little later). On lines 4, 22, and 23 I output some debug, but the rest of the function is just the algorithm for calculating Easter's date. It's quite an interesting problem, but in the end I come up with a friendly result string which I return to the caller on line 27 by passing it to the Hook.end function.

Now that I've made a Gist, I can make a new Hook. You can too! Just go to http://hook.io/new and give your hook a name and the URL of your Gist. I named my hook "easter". For the Hook Theme, I just chose "simple" but that doesn't matter a whole lot in my example. That was it - my hook was easy to create.

Now what? How do you actually run the thing? Your hooks can be accessed by URL at
http://hook.io/<your userid>/<hook name>
and the parameters can be passed along as parts of the query string. For example, if I want to run my Easter date hook and pass in a year of 1918, I would just go to the following URL:
http://hook.io/cwleonard/easter?year=1918
Note how the parameter is named year, which matches the field name I was looking for on line 3.

There's one minor gotcha here, and that is how the framework processes requests based on the HTTP Accept header. If I go to my hook's URL from my web browser, it won't actually run. Instead, it gives me a nice human-readable form to fill out and push a "run" button. This is because browsers generally set "Accept" to "text/html" and hook.io is trying to help me out by giving me a friendly form. To get around this, just put "&run=true" on the end of the URL. Don't worry, if you call the URL by other means (from JavaScript, curl, etc.) the "Accept" header won't be set to "text/html" (unless you want it to be) and it will run automatically. See the documentation for more details.

Now that I've covered all those details, try going to this URL:
http://hook.io/cwleonard/easter?year=1918&run=true
You should see a successful result and finally learn that Easter was on March 31st in 1918! Scroll down to the bottom of the results page and you can see the debug output as well.

A cool feature here is that if I find a bug in my code or just want to change my algorithm, all I have to do is edit the Gist. I don't have to do anything on the hook.io site, it just picks up the change next time I run.

One final thing I should explain is how hook.io is able to make that nice HTML form for you to enter the year if you forgot to add "&run=true" on the end. Note lines 31-35 of the Gist. They specify a schema for the hook, which allows forms to be automatically generated. My schema is extremely simple since I only have one field.

There are many interesting ways to use a microservice framework such as this. Imagine if you decomposed your big do-everything application into a bunch of small hooks and then tied them all together from a lightweight container. Even in my simple web comic, I have dozens of routes which could be broken out into microservices - one that just processes the images, one that serves comic data, one that updates the stats, etc.

Using curl to find Easter
You can even use command-line tools such as curl to access these microservices. The possibilities are endless! Use your imagination and come up with some cool new ideas!

I hope this simple example was helpful if you are going to try making some microservices of your own. There are a lot more features to the hook.io framework that you should check and and experiment with. Read the documentation (it's not that long!) to discover all the options. Remember, when you think big, think small.

Amphibian.com comic for 28 November 2014