AWS Lambda is an interesting service released by Amazon last year. It allows you to run some piece of code without having to worry about the underlying infrastructure or how it will scale, you code starts running after an event, and you get billed for the runtime only. Although I have not used it in any production environment yet, the concept is really amusing.

This weekend, I’ve been playing around with Lambda and Node.js. Testing it with node streams to create a “map-reduce” pipeline, and running arbitrary functions in a distributed environment without much work. I’ll focus on the latter one for now…

Arbitrary Functions in AWS Lambda

My goal here was to be able to “lambda-fy” a function in a way that you can run it locally, but with the option of running it remotely in AWS Lambda without really changing the original function. I did not look into making anything production-ready, as it was mainly a fun research.

By the end of this post, you’ll be able run your function func(args) in AWS Lambda by calling func.lambda(args).

This is what I quickly came up with:

1. Create new AWS Lambda Function

I started by creating a very simple Lambda function that would allow me to run whatever I wanted… Exactly, I went for a plain magical eval function.
You can easily create a new function by going to your AWS Console and opening the Lambda service.


exports.handler = function(event, context) {
  eval(event.data); //Eval whatever is sent on "data"
};

Yeah, hardcore. I called it “arbitrary” and moved on…
 
 
2. Create the Lambda-fy method

I’ve chosen to create this method by extending Lambda’s prototype with a method called lambda. It allows you to pass any function into which it will create a Lambda version of it. This version returns a promise (using bluebird).
Lambda’s context needs to be available to the function, so it can send the response back to our application (context.succeed), therefore it was explicitly passed into the scope.

var aws = require("aws-sdk");
var Promise = require("bluebird");

aws.Lambda.prototype.lambdafy = function(func) {
  var self = this;
  //Lambda version will be available under func.lambda(args)
  func.lambda = function() { 
    var args = Array.prototype.slice.call(arguments);
    return new Promise(function(resolve, reject) {
      self.invoke({
        FunctionName: 'arbitrary', //Sorry this is hardcoded
        Payload: JSON.stringify({ 
          data :"(function(context, args) { var func = " + func.toString() + "; func.apply(func, args); } )(context, event.args)",
          args: args 
        })
      }, function(err, data) {
        if (err !== null) reject(err);
        else resolve(data.Payload); 
      });
    });
  };
  return func;
};

Done! It should be ready to use!
Caveat: It might be clear, but I’ll bring it up anyway. For the function to work properly, it should be stateless (not depend from the state of the application that’s invoking it nor try to change its state).
 
 
3. An example of how to run it

Using the previous code, you can easily lambda-fy your functions, but you do need to make a tiny change in order for it to work within Lambda :(.
Here’s an example:


var lambda = new aws.Lambda({ region: 'us-east-1' });

function multiply(a, b) {
  var result = a*b
  if (typeof context === "object" 
     && typeof context.succeed === "function")
    context.succeed(result); //This will only run on AWS Lambda
  return result;
}

lambda.lambdafy(multiply); //Make it a "Lambda" function

console.log(multiply(3, 4)); //Run it locally

multiply.lambda(3, 4).then(function(data) { //Run it in AWS Lambda
  console.log(data);
});

I think it would be nice to update the lambdafy function, so no changes are needed to the target function, but this was the easiest way of supporting asynchronous functions as well. Anyway, I did get what I wanted out of this fiddling with Lambda… which is nice.

Feel free to tweak it around and let me know of improvements.

@AlexCorreia

Advertisements