A few of days ago, during a night without sleep, I decided to play a little bit with MessagePack-RPC for Java. It was absolutely a fun time but I ended feeling like it might be better. So I decided to make it more fun and I rewrote those client/server programs in Clojure.
As usual, those codes are on my GitHub account:
Those are two really simple samples, far from those of the Real World, of course, but them still worth enough to try the underlying concept and have fun.
One or two interesting things there
During my experiment, I realized some interesting things about compiling Clojure code ahead of time (AOT) and Java interop that I thought I might share briefly here. It happened due to the nature of the implementation of the MessagePack-RPC for Java library once it does data serialization and deserialization.
Here you will see compilation of Clojure code and what it results. If you want to run the samples, which is worth,you can find instructions in the project’s README.
So let’s look on…
Getting the Clojure project’s code
Once the whole project’s code is on GitHub, this is quite simple task:
$ git clone https://github.com/leandrosilva/msgpackrpc-sample-clojure
Important files in the project:
- project.clj – Leiningen project file where you can setup dependencies
- src/msgpackrpc_sample/server.clj – Server side of the RPC
- src/msgpackrpc_sample/client.clj – Client side of the RPC
In this blog post we are going to focus only on server.clj and client.clj.
Inspecting the server
Now you have the code, compile only the server code at first time.
$ lein compile msgpackrpc-sample.server
Ok. So what did the compilation above generated?
$ cd target/classes $ ls -la MathServer.class msgpackrpc_sample
The compilation generated:
- MathServer class
- And msgpackrpc_sample directory – which is the sanitized name for mesgpackrpc.sample namespace, as you can imagine
$ ls -la msgpackrpc_sample/ server$_add.class server$_div.class server$_main.class server$_mul.class server$_sub.class server$loading__4784__auto__.class server__init.class
Its content is:
- A class to load the namespace code
- A class to initialize the namespace
- And a class for every function included in the namespace
$ javap -c MathServer
Output:
public class MathServer extends java.lang.Object { public static {}; Code: ... public MathServer(); Code: ... public java.lang.Object clone(); Code: ... public int hashCode(); Code: ... public java.lang.String toString(); Code: ... public boolean equals(java.lang.Object); Code: ... public int add(int, int); Code: ... public int sub(int, int); Code: ... public int mul(int, int); Code: ... public double div(int, int); Code: ... }
I’m not sure whether you are familiarized with Clojure’s gen-class macro or not but it is the magic that has generated the Java code above.
(gen-class :name MathServer :methods [[add [int int] int] [sub [int int] int] [mul [int int] int] [div [int int] double]])
Putting it simple, the code above define like a public interface for MathServer class and the code below implements them, as follow.
(defn -add [this a b] (+ a b)) (defn -sub [this a b] (- a b)) (defn -mul [this a b] (* a b)) (defn -div [this a b] (/ a b))
This is a pretty much exciting thing, non? This is because if you need to interop your Clojure program/library with any Java program/library (legacy or not) which requires specific interface, you can design it quickly and unpainfuly using gen-class. Further more, you can design it up front, compile AOT, and finally delivery it as any regular Java .class file. Awesome!
Are you curious about the hyphen prefix to every function?
This is the notation to say that a given method implementation should bind to a function with its name plus hyphen prefix. It is possible to define a prefix other than hyphen, which is the default one, using :prefix option of gen-class macro. So if you define :prefix option as “banana-” for MathServer class, every function that implements a method of MathServer class should start with “banana-“.
You can find an example here.
Inspecting the client
The same way you did for server code, you have to do to the client code.
$ lein compile msgpackrpc-sample.client
Ok. Since you already did it before let’s move a little fast here (without verbiage of my end, which is good to you. :)).
$ cd target/classes $ ls -la IMath.class MathServer.class msgpackrpc_sample $ ls -la msgpackrpc_sample/ client$_main.class client$loading__4784__auto__.class client__init.class ifaces$loading__4784__auto__.class server$_add.class server$_div.class server$_main.class server$_mul.class server$_sub.class server$loading__4784__auto__.class server__init.class
I’m pretty sure you got it, since it is close to what you saw for server before, but I’d like to just comment a little bit. The compilation generated:
- IMath class, which is an interface actually
- Classes for client and ifaces namespaces, pretty much the same happened for server – note that those two are declared in the same file client.clj
So let’s inspect the IMath interface.
$ javap -c IMath
Output:
public interface IMath { public abstract int add(int, int); public abstract int sub(int, int); public abstract int mul(int, int); public abstract double div(int, int); }
This is the magic of gen-interface macro!
(gen-interface :name IMath :methods [[add [int int] int] [sub [int int] int] [mul [int int] int] [div [int int] double]])
And after that “magic”, this interface is imported like any other regular Java class, as follow.
(ns msgpackrpc-sample.client (:import IMath) (:import [org.msgpack.rpc.loop EventLoop]) (:import [org.msgpack.rpc Client]))
Wow! I love it, baby! Don’t you?
The End
There are yet other many interesting things on MessagePack, MessagePack-RPC and, of course, Clojure compilation, so I hope you have get interested on them as well, and go learn and try further more, because I definitely will do.
Enjoy it!