Spring remoting AMQP
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
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:
14:36:47.100 [main] INFO me.molchanoff.amqp.server.ServerMain - Started
14:36:47.251 [main] INFO o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@47f37ef1: startup date [Mon Mar 13 14:36:47 MSK 2017]; root of context hierarchy
14:36:47.585 [main] INFO m.m.amqp.server.SimpleInterfaceImpl - Node with ID: 1 started
14:36:47.817 [main] INFO o.s.c.s.DefaultLifecycleProcessor - Starting beans in phase -2147482648
14:36:47.818 [main] INFO o.s.c.s.DefaultLifecycleProcessor - Starting beans in phase 2147483647
14:36:47.907 [container-1] INFO o.s.a.r.c.CachingConnectionFactory - Created new connection: SimpleConnection@479759bd [delegate=amqp://guest@127.0.0.1:5672/, localPort= 55058]
14:37:10.050 [container-1] INFO m.m.amqp.server.SimpleInterfaceImpl - UUID 6dbf93bf-621d-4e7c-b703-908295f4507d generated on node 1 upon request from client 1
14:37:12.137 [container-1] INFO m.m.amqp.server.SimpleInterfaceImpl - UUID 48f1d401-42fa-44a0-af1e-5e189930514f generated on node 1 upon request from client 1
14:37:14.155 [container-1] INFO m.m.amqp.server.SimpleInterfaceImpl - UUID cbdbbd5c-6747-47bf-a15a-267066a40a16 generated on node 1 upon request from client 1
14:37:16.169 [container-1] INFO m.m.amqp.server.SimpleInterfaceImpl - UUID 3cb93958-d670-4a53-bf36-c1d663a771c3 generated on node 1 upon request from client 1
14:37:18.186 [container-1] INFO m.m.amqp.server.SimpleInterfaceImpl - UUID 75dc7c0b-2771-4305-ad8f-42c343f3e033 generated on node 1 upon request from client 1
14:37:20.205 [container-1] INFO m.m.amqp.server.SimpleInterfaceImpl - UUID 42688ce1-a51a-4647-80e0-477195347599 generated on node 1 upon request from client 1
14:37:22.222 [container-1] INFO m.m.amqp.server.SimpleInterfaceImpl - UUID 9a223770-b0fa-4f24-b3f9-b722b25364f0 generated on node 1 upon request from client 1
14:37:24.238 [container-1] INFO m.m.amqp.server.SimpleInterfaceImpl - UUID c23afd04-56bb-42ef-bd0d-6f95f9ba93ca generated on node 1 upon request from client 1
14:37:26.249 [container-1] INFO m.m.amqp.server.SimpleInterfaceImpl - UUID 4d917176-910d-4e2c-b53d-e65d61067c3d generated on node 1 upon request from client 1
14:37:28.262 [container-1] INFO m.m.amqp.server.SimpleInterfaceImpl - UUID a59f7637-a557-42db-ab72-851a38e8e2a4 generated on node 1 upon request from client 1
Client application output:
14:37:09.182 [main] INFO me.molchanoff.amqp.client.ClientMain - Started
14:37:09.370 [main] INFO o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@47f37ef1: startup date [Mon Mar 13 14:37:09 MSK 2017]; root of context hierarchy
14:37:09.771 [main] INFO o.s.c.s.DefaultLifecycleProcessor - Starting beans in phase -2147482648
14:37:09.875 [main] INFO o.s.a.r.c.CachingConnectionFactory - Created new connection: SimpleConnection@3c3d9b6b [delegate=amqp://guest@0:0:0:0:0:0:0:1:5672/, localPort= 55061]
14:37:10.126 [main] INFO me.molchanoff.amqp.client.ClientMain - Client 1 received payload: 6dbf93bf-621d-4e7c-b703-908295f4507d
14:37:12.142 [main] INFO me.molchanoff.amqp.client.ClientMain - Client 1 received payload: 48f1d401-42fa-44a0-af1e-5e189930514f
14:37:14.160 [main] INFO me.molchanoff.amqp.client.ClientMain - Client 1 received payload: cbdbbd5c-6747-47bf-a15a-267066a40a16
14:37:16.173 [main] INFO me.molchanoff.amqp.client.ClientMain - Client 1 received payload: 3cb93958-d670-4a53-bf36-c1d663a771c3
14:37:18.191 [main] INFO me.molchanoff.amqp.client.ClientMain - Client 1 received payload: 75dc7c0b-2771-4305-ad8f-42c343f3e033
14:37:20.210 [main] INFO me.molchanoff.amqp.client.ClientMain - Client 1 received payload: 42688ce1-a51a-4647-80e0-477195347599
14:37:22.226 [main] INFO me.molchanoff.amqp.client.ClientMain - Client 1 received payload: 9a223770-b0fa-4f24-b3f9-b722b25364f0
14:37:24.242 [main] INFO me.molchanoff.amqp.client.ClientMain - Client 1 received payload: c23afd04-56bb-42ef-bd0d-6f95f9ba93ca
14:37:26.254 [main] INFO me.molchanoff.amqp.client.ClientMain - Client 1 received payload: 4d917176-910d-4e2c-b53d-e65d61067c3d
14:37:28.267 [main] INFO me.molchanoff.amqp.client.ClientMain - Client 1 received payload: a59f7637-a557-42db-ab72-851a38e8e2a4
This project is available on my GitHub repository. Try yourself with more clients and servers and see the difference.