Reactive Ruby: Building Real-time Apps with JRuby and Ratpack

Listen to this article

Nothing beats Ruby when it comes to rapid development, quick feedback, and delightful coding. The Ruby runtime and traditional ruby frameworks favor synchronous programming, which makes them easy to use and understand. But microservices and real-time apps require asynchronous programming and non-blocking IO to enable maximum throughput. That's where JRuby comes in.

You can build reactive microservices in Ruby using JRuby and frameworks like Ratpack. JRuby interprets Ruby code into Java Virtual Machine (JVM) bytecode to gain the performance and concurrency benefits of Java without writing any Java code or XML. But the performance benefits of the JVM are just the beginning. You can also use JRuby to leverage libraries like Ratpack, a micro-framework for building modern HTTP applications with reactive principles in mind.

Together, JRuby and Ratpack make an excellent platform for reactive systems. In a moment, you’ll learn how to build such an app, but we first need to define this “reactive” buzzword.

The term “reactive” implies many different characteristics, such as applying backpressure from a client. But another important characteristic is an ability to perform non-blocking communication. When a thread of execution in a reactive application waits for an external service (like a database or other microservice) the thread will release itself to go do other work instead of blocking and waiting for the response.

The difference between blocking and non-blocking looks like this:

A reactive system increases throughput by allowing the server thread to do other work while the database is processing its response. In this way, it can more fully saturate the CPU and improve performance. To implement this kind of architecture, you need a runtime and framework that are built from the ground up to support non-blocking IO.

JRuby supports Rails, Sinatra, Puma, Eventmachine, Sidekiq, and almost all of your favorite CRuby libraries and frameworks. But JRuby also opens the door to new technologies. Among those new technologies is Ratpack, which is loosely comparable to Sinatra. Unlike Sinatra, however, it leverages the powerful concurrency libraries of the JVM to provide an interface with first-class support for asynchronous programming and non-blocking IO. Ratpack is a JVM framework, but you can use it with your favorite Ruby tools.

To create a JRuby Ratpack app, you’ll need to install JRuby. You can download the binaries for your platform from the JRuby website or run rvm install jruby if you’re using RVM.

Now create a directory for your app, move into it, and create a Gemfile with these contents:

source "https://rubygems.org" ruby '2.3.0', :engine => 'jruby', :engine_version => '9.1.1.0' gem "jbundler", "0.9.2"

This adds JBundler, which installs Java dependencies as if they were Ruby Gems. Initialize Bundler and JBundler by running these commands:

$ jgem install bundler $ jruby -S bundle install --binstubs

JBundler uses a Jarfile to manage dependencies. Create this file alongside your Gemfile , and put the following code in it:

jar 'io.ratpack:ratpack-core', '1.3.3' jar 'org.slf4j:slf4j-simple', '1.7.10'

Then run JBundler to install the JAR dependencies:

$ bin/jbundle install

Now you’re ready to create an app. In the same directory as the Gemfile and Jarfile , create a server.rb file and put the following code in it:

require 'java' require 'jruby/core_ext' require 'bundler/setup' Bundler.require java_import 'ratpack.server.RatpackServer' RatpackServer.start do |b| b.handlers do |chain| chain.get do |ctx| ctx.render("Hello from Ratpack+JRuby") end end end

This creates a Ratpack server, and defines a single get handler in the handler chain, which is a Ratpack concept used to facilitate asynchronous and non-blocking IO. This handler renders the string "Hello from Ratpack+JRuby" for the default / route.

Save the file, and start the server by running this command:

$ jruby server.rb

Open a browser and point it to http://localhost:5050 , and you’ll see the output rendered by the handler.

This is a great start, but it doesn’t demonstrate the full power of Ratpack. Let’s add a streaming service.

Open the server.rb file again, and add these statements immediately after the first java_import :

java_import 'ratpack.stream.Streams' java_import 'ratpack.http.ResponseChunks' java_import 'java.time.Duration'

Then add the following code to the b.handlers block after the first get handler:

chain.get("stream") do |ctx| publisher = Streams.periodically(ctx, Duration.ofMillis(1000)) do |i| i < 10 ? i.to_s : nil end ctx.render(ResponseChunks.stringChunks(publisher)) end

This creates another get handler, but on the /stream route. It will stream a series of numbers to the client without blocking the request thread. When the stream is paused (every 1000 milliseconds) it will release the thread so it can do other work. This is particularly beneficial when the stream needs to consume data from a remote resource.

Start the app again with the jruby server.rb command. Then browse to http://localhost:5050/stream and you’ll see the integers rendered every 1000 milliseconds.

You’re starting to unlock the power of a reactive system. Not only does this service periodically publish new items, it will also apply back pressure based heuristics. But there is much more Ratpack can do. Let’s add a database to the application to make use of some more reactive features.

JRuby works with many popular Ruby database clients including ActiveRecord and Sequel. In this example, we’ll use Sequel to interact with a single table mapped to a Ruby class. Database queries and updates can be expensive so we’ll wrap them with Ratpack Promises to prevent the IO operations from blocking the request thread.

To set up Sequel you’ll need a database migration, a Rake task, a model class, and of course a database. Rather than write all of this from scratch, you can deploy the example app to Heroku by clicking this button:

The example app extends the application you’ve already created, so it will look familiar. After you’ve deployed it, get a local copy of the app by running these commands (but replace with the name of your Heroku app):

$ git clone https://github.com/jkutner/ratpack-jruby-example $ cd ratpack-jruby-example $ heroku git:remote <app-name>

Open the project’s server.rb file, and you’ll see some new handlers that look like this:

c1.get do |ctx| Blocking.get do DB[:widgets].all end.then do |widgets| ctx.render(JSON.dump(widgets)) end end

The Blocking.get method is part of the Ratpack framework. It returns a Promise, which executes a Ruby block asynchronously. A promise is a unit of work queued to be executed, The work is executed when some other code subscribes to the promise by calling the then method. After the Promise is executed, the block passed to the then method is executed.

This API is inspired by Node.js and it’s popular event-driven architecture. But unlike Node.js, Ratpack's execution model guarantees execution order. It can also detect when there is no more user code to execute, which means it can notify the user of some issue on the server side. Ratpack and JRuby also have the benefit of a multithreaded platform in the JVM. This characteristic makes JRuby and Ratpack a great platform for real-time apps. Building real-time apps Real-time web apps update users with new data as soon as it’s available, rather than requiring them to ask for it or refresh a page. The classic example is a chat application.

In many cases, real-time apps are implemented using Websockets. To demonstrate this with Ratpack, you could modify the steaming example you created earlier to use the WebSockets.websocketBroadcast() method to broadcast the output. For example:

WebSockets.websocketBroadcast(ctx, publisher)

Then with the appropriate client-side code, you could consume this streaming service.

But Ratpack also includes a built-in module that uses Websockets to stream metrics data for your application's runtime. It uses Dropwizard Metrics, another JVM library to capture memory usage, response time, and other data. To use the module, switch your Git repo to the “metrics” branch by running this command:

$ git checkout -t origin/metrics

In the server.rb file, you’ll see this code, which sets up the Dropwizard module:

b.server_config do |config| config.base_dir(BaseDir.find) config.props("application.properties") config.require("/metrics", DropwizardMetricsConfig.java_class) end b.registry(Guice.registry { |s| s.module(DropwizardMetricsModule.new) })

In the handlers block, you’ll see this code, which sets up the metrics view and the websocket handler:

chain.files do |f| f.dir("public").indexFiles("metrics.html") end chain.get("metrics-report", MetricsWebsocketBroadcastHandler.new)

The client-side, in metrics.html and metrics.js , is fairly standard Websocket code.

Deploy the metrics branch to Heroku by running this command:

$ git push -f heroku metrics:master

Then open the metrics view by running this command:

$ heroku open metrics.html

You’ll see the dynamically generated gauges and graphs that represent the real runtime data for your application. Make a few requests to the app, and you’ll notice that they change on their own.

Ratpack opens the door for powerful and modern Ruby applications that are both pleasant to develop and reactive. Under the hood, Ratpack uses Netty, an asynchronous event-driven networking framework, which is the cutting edge of server technology.

Ratpack is a great way to take advantage of many modern architectural patterns without giving up the Ruby language you love.

Joe Kutner is the author of Deploying with JRuby 9k and the JVM Platform Owner at Heroku.