2013年7月30日星期二

Introducing Roger: A Java RPC Framework using RabbitMQ & Jackson

As my colleague Patrick mentioned in our previous post, we at Oversee’s Retail Group have been using RabbitMQ as the communication layer for the remote procedure calls done from the UI (written in Python) to the backend (written in Java). In Patrick’s post, he described his homegrown Python framework, Chu, for quickly and easily creating RPC clients within Tornado. This post is all about the Java framework, Roger, for creating stand-alone RPC servers.
Our legacy website backends used Java Servlets to send JSON-formatted RPC responses to the Python UI. While we may have moved away from using Servlets and HTTP (mainly for load-balancing reasons as detailed in the previous post), we have always been happy with JSON as a data serialization format. If you’re writing Java, theJackson JSON library is currently the uncontested choice for parsing & creating JSON. Add on the Jackson Mapper, and you can write all of your code using Plain-Old-Java-Objects, while barely aware of the fact that JSON serialization & deserialization is happening automatically. So, Roger uses Jackson’s ObjectMapper for all of its message serialization & deserialization. And if you want more fine grained control over how your Java Objects turn into JSON, you can add JacksonAnnotations to your request & response objects.
In the code example below we are creating a stand-alone RPC Server.
package com.shopwiki.roger.example;
 
import org.codehaus.jackson.type.TypeReference;
 
import com.rabbitmq.client.Address;
import com.shopwiki.roger.RabbitConnector;
import com.shopwiki.roger.rpc.*;
 
public class ExampleRpcServer {
 
    public static class Request {
        public String name;
    }
 
    public static class Response {
        public final String greeting;
        public Response(String greeting) { this.greeting = greeting; }
    }
 
    public static void main(String[] args) throws Exception {
 
        RequestHandler<Request, Response> handler = new RequestHandler<Request, Response>() {
            @Override
            public TypeReference<Request> getRequestType() {
                return new TypeReference<Request>() {};
            }
 
            @Override
            public Response handleRequest(Request request) throws Exception {
                return new Response("Hello " + request.name + "!");
            }
        };
 
        RabbitConnector connector = new RabbitConnector(new Address("localhost"));
 
        WorkerFactory factory = new BasicWorkerFactory(connector, 2);
        factory.addHandler("HelloWorld", handler);
 
        RpcServer server = new RpcServer(factory, "RpcExample_");
        server.start();
    }
}
  • We start by defining simple Request and Response POJO classes.
    • Request objects are created by Jackson deserializing a JSON message.
    • Responses are created manually and serialized to JSON by Jackson.
  • We begin our main method by defining a single RequestHandler that takes a “name” parameter from a Request and returns a “Hello!” greeting in a Response object.
    • Also, take note of the getReqestType() method that returns a TypeReference. This is required by Jackson to know which class to instantiate when deserializing requests (even works with generics).
  • The RabbitConnector is just a Connection factory that the RpcServer uses to make an initial connection to RabbitMQ and also reconnect, should the connection ever break.
  • The BasicWorkerFactory is the default implementation of a WorkerFactory. It creates 2 consumer threads for the RequestHandler to use.
    • We’ve added our handler to the factory and named it “HelloWord”, which is used as a suffix for the request-queue.
  • Lastly, we instantiate an instance of RpcServer, passing it our WorkerFactory, and a queue-name prefix for our server.
    • If our RpcServer had multiple handlers, each of their queue-names would start with the prefix.
If you’d like to try out this example, you can use the Python example from the post on Chu as a client. However, Roger also has an RpcClient class you can use to test your RpcServers. See the example code on GitHub.
Please note, that to run the example, you will need a RabbitMQ server running on your local machine with a queue named “RpcExample_HelloWorld”. If you’d like your RPC request-queues to be generated automatically, please see the QueueDeclarator in the slightly longer ExampleRpcServer.
Hopefully you can see how easy Roger makes it to whip up an RPC server using RabbitMQ. Our Java team currently uses Roger in production to power five different websites. This framework gives us a low overhead request-response cycle, built-in load balancing, scales horizontally with ease, and allows us to easily mix and match our UI’s to different API’s simply by changing routing-key bindings. We think RabbitMQ and Jackson are a great pairing for RPC and hope you’ll give Roger a try.

没有评论:

发表评论