Railway IPC Part II: Synchronous Messaging

Sophie DeBenedetto
Oct 10, 2019 · 6 min read

Building RPC Features with Railway

In a previous post, we introduced the Railway family of libraries for building messaging patterns between applications on our Learn.co ecosystem. We saw how these tools allowed us build robust async messaging systems in Elixir and Ruby with very little hand-rolled code. In this post, we'll learn how to use Railway to enact synchronous messaging patterns, also knows as 'RPC' (Remote Procedure Calls).

The Problem: A Scary Story

Before we talk about what RPC is and how Railway allows us to enact it, I want to share a scary story. If you recall from our last post, we are responsible for building out the a feature in which an admin user of our Course Conductor app creates a student cohort, thereby sending a message to our Learn.co LMS to create a corresponding "batch" through which students will access lessons, and a corresponding message to our Registrar app through which Admissions reps can admit students.

Image for post
Image for post
Image for post
Image for post

The Solution

RPC messaging, or Remote Produce Call, is a pattern in which one application can request the execution of a service in another application. The Railway libraries expose an RPC API that allows one application to make a synchronous remote produce call to another application and get make the requested info.

Building RPC Features with Railway

Here's a look at the feature we're going to support, per our spooky story.

Image for post
Image for post

Defining an RPC Client in Elixir

We will define a publisher module that uses the Railway.Publisher behavior and knows the queue and exchange of the server to which it will publish the request. Under the hood, Railway will establish this queue and bind it to the exchange if no such queue exists.

Defining an RPC Server in Ruby

We need to do the following to create our RPC server:

  • Specify the message it is expecting and the service it will use to respond, i.e. the "responder"
  • Define the responder class
  • Specify the error adapter class
  • Define the error adapter class

Specifying the Subscription Queue

We will define a server class that inherits from RailwayIpc::Server and knows the queue to which to subscribe.

Specifying the Expected Message and Responder

Now we define the type of message our server expects to receive and the responder class with which it will respond. Here we're expecting a protobuf message, LearnIpc::Requests::Batches.

Defining the Responder Class

We define a responder class that inherits from RailwayIpc::Responder and implements a respond method that takes a block. This block must return a LearnIpc::Documents protobuf message instance.

Specifying the Error Adapter

The error adapter is responsible for telling your Railway server what kind of error message protobuf to respond with in the event of an error processing the request.

Defining the Error Adapter

Our Error adapter must conform to the following API:

  • Return the protobuf error message instance of your choosing.

Defining an RPC Client in Ruby

In order to define our client, we need to do three things:

  • Specify the type of message we expect to receive in response
  • Specify the error adapter class

Specifying the Queue and Exchange

We define a client class that inherits from the RailwayIpc::Client and publishes to a given queue and exchange.

Specifying the Expected Response Message

Next, we will use the handle_response class method to define the type of message we are expecting the corresponding server to send back.

Specifying the Error Adapter

Lastly, we need to specify the error adapter that our client should use in the event of a client-side RPC failure:

Publishing the Message

Now our client is ready to send it RPC request using the request method:

Defining an RPC Server in Elixir

To define our server, we need to do three things:

  • Implement a handle_in/1 function that matches the expected message
  • Respond with a protobuf document message

Specifying the Queue and Exchange

Our RPC consumer uses the RailwayIpc.RequestsConsumer behaviour and knows its queue and exchange.

Implementing the handle_in/1 Function

Our consumer knows how handle the LearnIpc.Requests.Cohorts protobuf request message.

Responding with a Protobuf Document

Our consumer responds with the LearnIpc.Documents.Cohorts protobuf document message.

Conclusion

The Railway libraries allow us build out robust synchronous messaging systems in Elixir and Ruby with very little hand-rolled code. And since all communication is backed by RabbitMQ, we don't need to worry about building authentication flows like we would with HTTP.

Image for post
Image for post
Image for post
Image for post

Flatiron Labs

We're the technology team at The Flatiron School (a WeWork…

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store