Bridging Rails & Node.js with gRPC

--

Some tools are just better fits for the job at hand. We ❤ building UIs in React, but as we evolved from a largely server side rendered application to a mix of client and server side rendered views we found ourselves duplicating UI code to make sure that our layouts didn’t reshuffle as the client renderer took over. We solved that problem with a small Node.js service that provides server side rendering for our React components in Rails views. Today that service handles over a thousand requests a minute and can return most components in around 2ms.

Originally that service communicated with our Rails application via a unix socket. It was a simple and fast way to get data to our Node.js service and back to the Rails application with very little overhead. While fast, this requires us to deploy our React Rendering Engine (RRE) alongside each one of our application servers. This requirement complicates our deployment and scaling story, so we started to look for a replacement communication medium.

gRPC

gRPC is a high performance open source RPC framework from Google. It complements their protocol buffer serialization format by defining service interactions in addition to messaging formats. We use protocol buffers to define schemas in our analytics pipeline, tracking pixels, and we’ve started using it for some of our internal service communication. Protocol buffers solve one part of the growing pains of internal service communication by defining hard contracts and a speedy serialization format, but doesn’t solve some of the other problems like endpoint discovery, documentation, or having shared high quality client and server code in a number of different languages.

Generating Client and Server Code

Defining the interface for our React Rendering Engine in a .proto file is simple.

rre.proto

We define messages for the request and response. These will autogenerate structs that we can use in our Ruby and Node.js applications to help maintain the contract between the services. The service portion defines the RPC interface between client and server.

Using the grpc tools we can autogenerate client and service stubs for us to use in both Ruby and Node.js, but first we’ll need to install a few dependencies.

Generating message structs for both Node.js and Ruby requires you to have the protoc compiler installed.

brew install protobuf

To generate the Ruby gRPC code you’ll need the grpc-tools and for both Node.js and Ruby you’ll need to add the gRPC library to your project.

yarn add grpc

And for Ruby:

bundle add grpc grpc-tools

Generating Ruby Code

Alright let’s generate some client and server code!

bundle exec grpc_tools_ruby_protoc -I lib/ — ruby_out=./lib/ — grpc_out=./lib/ rre/rre.proto

Not too bad it’s almost as if I actually wrote it 😀

gRPC generates a fully functional client and stubs to implement for the service. They provide great documentation on how to implement these service stubs in a variety of different languages.

Now that our client is done, let’s write out some server code. In the Node.js version of gRPC we don’t need to generate a thing, just pull in the service definition:

Okay now that I’ve got my service running, let’s call it with my Ruby client.

And there we go. I now have my Rails and Node.js service talking and I’ve barely written any actual code. All done, right? We thought so too until we shipped our first version to our staging environment where everything ground to a halt.

What the Fork?!

For those of you using a forking application server on the Ruby side like Unicorn or Puma, you’ll need to make sure that you initialize your clients in an after_fork hook. If you start your clients before you fork, the gRPC framework will timeout all of your calls. You can read more about this issue on the gRPC github tracker.

Our Bright gRPC Filled Future?

Our first foray with gRPC has been a great alternative to hand rolling http clients that use JSON over the wire for internal service communication. gRPC also provides a number of new features that we’re eager to try in our stack: async clients, streaming messages, and a very high throughput service library. For internal service communication in a polyglot environment, gRPC provides excellent tooling and documentation and I suspect we’ll be continuing to experiment with it as we grow.

If you have a passion for building great products, we’re always looking for folks to join our band here at Reverb.

Questions?

Erik Benoist

--

--