Introducing Jets: A Ruby Serverless Framework on AWS Lambda

Tung Nguyen
BoltOps

--

Jets is a framework that allows you to build serverless applications in a beautiful language: Ruby. It includes everything needed to build and deploy applications to AWS Lambda. I love working with Rails, Ruby and AWS; and wanted to work with something similar in the serverless world. So I built Jets.

It is key to understand AWS Lambda and API Gateway in order to understand Jets conceptually. Jets maps your code to Lambda functions and API Gateway resources.

  • AWS Lambda provides Functions as a Service. It allows you to upload and run functions without worrying about the underlying infrastructure.
  • API Gateway is the routing layer for Lambda. It is used to route REST URL endpoints to Lambda functions.

You write code and Jets turns the code into AWS Lambda functions and API Gateway resources.

Controllers

Here’s the first example of Jets code, a controller:

app/controllers/posts_controller.rb:

class PostsController < ApplicationController
def index
# renders Lambda Proxy structure compatiable with API Gateway
render json: {hello: "world", action: "index"}
end
def show
id = params[:id] # params available
# puts goes to the lambda logs
puts event # raw lambda event available
render json: {action: "show", id: id}
end
end

If you’re familiar with Rails and Sinatra, this will look familiar. Jets takes each controller’s public methods and turns them into Lambda functions. Here are the functions in the Lambda console:

Routes

Here’s what a routes file could look like:

config/routes.rb:

Jets.application.routes.draw do
get "posts", to: "posts#index"
get "posts/new", to: "posts#new"
get "posts/:id", to: "posts#show"
post "posts", to: "posts#create"
get "posts/:id/edit", to: "posts#edit"
put "posts", to: "posts#update"
delete "posts", to: "posts#delete"
end

Jets takes the routes file, creates the corresponding API Gateway resources, and associates them with Lambda functions. Here are the routes in the API Gateway console:

Jobs

Jets also supports asynchronous jobs that work outside the web request-response cycle. Job code looks like:

app/jobs/hard_job.rb:

class HardJob < ApplicationJob
rate "10 hours" # every 10 hours
def dig
{done: "digging"}
end
cron "0 */12 * * ? *" # every 12 hours
def lift
{done: "lifting"}
end
end

The code above creates Lambda functions and CloudWatch event rules to handle the scheduling of work.

You can check for the job functions in the Lambda console:

You can also see the associated CloudWatch Event Rule in the CloudWatch console:

Project structure

Here’s what a Jets project structure looks like.

.
├── app
│ ├── controllers
│ ├── helpers
│ ├── javascript
│ ├── jobs
│ ├── models
│ └── views
├── bin
├── config
├── db
├── public
└── spec

We have the traditional MVC folders: app/models, app/views, and app/controllers. The config folder contains your application’s configuration settings. Further explanation for each folder is provided on the Project Structure docs.

How Jets Works

AWS Lambda does not support Ruby yet, so how is this possible?

Simple. Ruby support is added by using a node shim. The shim is written a language that is natively supported by AWS Lambda and essentially calls out to Ruby.

Native-like Performance

What about speed? Is it slow?

Jets offers performance comparable to languages natively supported by Lambda. Jets loads Ruby into the Lambda execution context and reuses it, essentially giving Ruby native performance. Here’s a quick performance comparison:

Ruby function speed:

$ time curl -so /dev/null https://1192eablz8.execute-api.us-west-2.amazonaws.com/dev/ruby_example
real 0m0.164s
user 0m0.039s
sys 0m0.063s

Python function speed:

$ time curl -so /dev/null https://1192eablz8.execute-api.us-west-2.amazonaws.com/dev/python_example
real 0m0.178s
user 0m0.047s
sys 0m0.054s

In the case above, the Ruby function happened to be faster than the Python function. Generally, it’s a tie. More info here: Jets Native-like Performance.

Debugging Ruby Errors

What about debugging? Is it complicated?

You might think that the shim can complicate debugging but Jets makes the shim transparent. You can debug your application just like you would any application written in a language that’s natively supported by AWS Lambda. Jets surfaces Ruby errors directly to the Lambda console. Here’s an example of Ruby code throwing an intentional error:

class PostsController < ApplicationController
# ...
def ruby_example_error
INTENTIONAL_RUBY_ERROR
render json: {message: "hello from ruby #{RUBY_VERSION}"}
end
end

Here’s what the stack trace appears like in the Lambda Console.

There is no mental context switching. You stay in Ruby-land the entire time.

Quick Start

Here are commands that generate a CRUD app to get you started:

gem install jets
jets new demo
cd demo
jets generate scaffold Post title:string
vim .env.development # edit with local db settings
jets db:create db:migrate
jets server

The jets server command starts a server that mimics API Gateway so you can test locally. Open http://localhost:8888/posts and test out the CRUD site created from the scaffold.

When you’re ready, adjust your .env.development.remote with an RDS database and deploy to AWS Lambda.

$ vim .env.development.remote # adjust with remote db settings
$ jets deploy
API Gateway Endpoint: https://puc3xyk4cj.execute-api.us-west-2.amazonaws.com/dev/

You should see something like this:

Lambda Functions:

API Gateway:

The app itself:

Here’s a live Demo. Note, the example records automatically get deleted and reseeded daily.

More to Cover

There’s lots more to cover but to keep this post at a reasonable length, I’ll summarize some more of what Jets has to offer:

  • Prewarming Support: Remedies the Lambda cold start problem.
  • Local Server: A server which mimics API Gateway that allows you to test locally.
  • Jets Call: Remote and local testing of the functions directly.
  • REPL Console: A REPL jets console that allows you to test things out in an interactive shell quickly.
  • Database Support: Supports ActiveRecord based databases, PostgreSQL currently, and DynamoDB.
  • Polymorphic Support: Ability to write your lambda functions in other languages like python or node for certain uses cases.
  • Function Properties: Allows you to set Lambda function properties application-wide, at the class-level, or individually at the function level.
  • Deployment: A deploy command that handles the mundane task of packaging and deploying your code to AWS Lambda.

For more info check out the documentation site rubyonjets.com and the CLI reference.

Hoping to post more on Jets in the future. Feel that I’ve been lucky enough to be able to combine a lot of learnings from over the years to make Jets. Hope you like Jets and give it a try. Also if you find Jets interesting, please give it ⭐️ it on GitHub. 👍

Jets Tutorial Series

Thanks for reading this far. If you found this article useful, I’d really appreciate it if you share this article so others can find it too! Thanks 😁 Also follow me on LinkedIn.

Got questions? Check out BoltOps.

Originally published at blog.boltops.com on August 18, 2018.

--

--