Spring remoting AMQP

1+

In this post I’ll show how to implement simple Java RPC-based application with Spring remoting AMQP. Usually when building complex enterprise systems you need your application to be distributed rather than monolithic.

Imagine a web application with three modules:

  • Front-end module, which serves user request and renders resulting HTML page.
  • REST API module, which provides common interface to interact with your application.
  • Back-end module where all business logic is performed.

In this example, Back-end acts as a server and Front-end and Rest API are both clients. As a Java developer you often face with several remote technologies:

  • RMI
  • Hessian/Burlap
  • JAX-WS (JAX-RPC)
  • JMS
  • AMQP

You can see all remote technologies supported by Spring Framework here: Remoting and web services using Spring.

JMS/AMQP

I’d like to mention the last two options: JMS and AMQP. These technologies use message broker as an intermediate member. It has some advantages over other technologies. In RMI, for instance, your clients have to know the address of RMI registry. If you have three RMI registry servers, all your clients have to know their addresses.

As opposed to RMI, when using message-based technology, all your members should know the address of message broker only. They don’t need to know about each other at all! Message broker itself does all routing routine.

The difference between JMS and AMQP is that JMS is a standard Java messaging API and AMQP is a standard messaging protocol. I won’t dive deeper into details, there’s a good article: Understanding the Differences between AMQP & JMS.

In this article I’ll show how to implement RPC-based application using Spring and AMQP. We will use RabbitMQ message broker for this, running in Docker container.

RabbitMQ RPC overview

RabbitMQ RPC diagram
Figure 1: RabbitMQ RPC diagram

In our example we will have Server and Client modules. We can have as many clients and servers as we want! Again, message broker handles all routine. Adding new client or server to our system is as simple as just starting new instance of respective module. No additional configuration required.

We have a Java interface with only one method:

When client calls this method, server generates new UUID and returns it back to client. Simple.

Server with message listener, serving SimpleInterface method invocation is bound to “SimpleInterface” queue. On the other side, this queue is bound to rpc exchange with SimpleInterface routing key.

Client sends a message to rpc exchange with SimpleInterface routing key and RabbitMQ message broker routes this message to SimpleInterface queue according to our binding.

Okay, let’s start writing our server module. I’ll use Gradle as a build system.

Add these libraries to your dependencies:

Writing server application

Here’s our SimpleInterface implementation:

And here’s our Spring Java configuration:

As you can see here, RabbitMQ has to be running on standard 5672 port. We use “node” JVM property to pass node number as argument when starting our server app. In this config we also declare our exchange, queue and binding. Note, that we have to declare RabbitAdmin bean in order to get our exchange, queue and binding automatically created. The key bean here is AmqpInvokerServiceExporter and SimpleMessageListenerContainer. You need to declare every of them for every interface you want to export with Spring.

And finally, our main class:

Writing client application

Spring Java configuration for client app will be much simplier:

Here AmqpProxyFactoryBean is used to create proxy object for exported interface. You have to declare this bean for every exported interface you want to use.

Finally, our main class:

Launching applications

You can get full example project on GitHub here. In this example we will run RabbitMQ in Docker container. Official image is used: RabbitMQ Docker image.

If you have Docker installed, type $ docker-compose up to start RabbitMQ. You can then build project by typing $ ./gradlew jar.

After you have RabbitMQ running, you can launch as many client and server applications as you need. Just pass -Dclient={client#} JVM argument to client and -Dnode={node#} to server. These arguments are used to identify which client sent request and which server processed that request.

Launch server and client applications:

$ java -Dnode=1 -jar server/build/libs/server-1.0.jar

$ java -Dclient=1 -jar client/build/libs/client-1.0.jar

Server application output

Client application output:

This project is available on my GitHub repository. Try yourself with more clients and servers and see the difference.

1+