Getting Started
Query/Response for Spring® AMQP makes it really easy to extend Spring Boot stand-alone, production-grade applications, that are using Spring AMQP. We have taken a working pattern for building highly decoupled evolving service architectures, and wrapped it in a developer friendly library.
System Requirements
Query/Response for Spring® AMQP requires at least Spring Boot 2.x and Java 11, and should work for later releases too. We are building and running it successfully with Java 11 and the Spring Boot 3.0.3 version.
Installation & Configuration
It is distributed as a Maven dependency, and is known to work well with Maven 3.3+. Using the dependency with Gradle should work too. Please see the Quickstart information, available on the project Github page, for information on how to get the Maven dependency.
Enabling Query/Response for Spring® AMQP is done by loading the QueryResponseConfiguration
class. The most simple way to do this, is by annotating your Spring Boot application with the @EnableQueryResponse
annotation.
@SpringBootApplication
@EnableQueryResponse
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
NOTE: This annotation will do nothing more but to import the QueryResponseConfiguration
class.
That's it! There is no more infrastructure code, wiring or setup that needs to be done. It's just that easy.
Connecting to an AMQP broker
Before you can run your application you need to make sure there is an AMQP broker available. By default Query/Response for Spring® AMQP tries to connect to a https://www.rabbitmq.com[RabbitMQ], running locally on port 5672
.
Start an and run RabbitMQ using docker
:
$ docker run -p 5672:5672 -p 15672:15672 rabbitmq:3-management
NOTE: The 3-management
tag will enable the RabbitMQ Management UI. When the broker is running, it can be accessed at http://localhost:15672 with username and password guest/guest
.
Now running your application, will enable Query/Response for Spring® AMQP, connect to the broker and create all the resources necessary on the broker.
$ mvn spring-boot:run
Now is a good time to use the RabbitMQ Management UI, available at http://localhost:15672, to inspect the exchange, queues and bindings created by Query/Response for Spring® AMQP by default.
Queries
Publishing queries is a way for your application to ask for information that it may need in order to accomplish tasks. Queries express a need, and are not addressed to any specific service or component.
Query/Response for Spring® AMQP makes it really really easy, to create and publish a query using the QueryBuilder
.
@Component
public class Queries {
@Autowired
QueryBuilder queryBuilder;
@Order(2)
@EventListener(ApplicationReadyEvent.class)
public void query() {
Collection<String> polos =
queryBuilder.queryFor("marco", String.class) // <1>
.waitingFor(1000L) // <2>
.orEmpty(); // <3>
polos.stream().map("marco? "::concat).forEach(System.out::println);
}
}
Note
We are using the @Order
annotation in our example only to ensure that responses are built and registered before queries, when they are built in one and the same app.
<1>
Initiates a query for the term marco
, with any results being consumed as, or mapped to, the type String.class
. Returned results are always gathered in a collection. Either none, one or many elements may be returned.
<2>
Queries require a timeout, here we set it to 1000L
milliseconds. This means that this specific query will always block for 1 second.
<3>
The query may not receive any responses, so it always needs to specify how that case should be handled. Default here is an empty collection, of the declared return type String.class
.
Hopefully this shows, how concise and powerful the QueryBuilder
is, dealing with results mapping, fault tolerance and default values in just a couple of lines of code.
If you run the application now, it will publish a query to the message broker, which we can see in the logs.
$ mvn spring-boot:run
...
c.s.queryresponse.RabbitFacade : |<-- Published query: marco - (Body:'{}' MessageProperties [headers={x-qr-published=1589642002076}, replyTo=94f0fff4-c4f3-4491-831d-00809edb6f95, contentType=application/json, contentLength=2, deliveryMode=NON_PERSISTENT, priority=0, deliveryTag=0])
At the moment there are no responses to be consumed, so after blocking for 1 second, nothing is printed STDOUT
.
Responses
Building services, medium, large or micro (who cares), that publish responses to queries is also really easy with Query/Response for Spring® AMQP, using the ResponseBuilder
.
@Component
public class Responses {
@Autowired
ResponseBuilder responseBuilder;
@Order(1)
@EventListener(ApplicationReadyEvent.class)
public void response() {
responseBuilder.respondTo("marco", String.class) // <1>
.withAll() // <2>
.from("polo", "yolo"); // <3>
}
}
<1>
Initializes a response to queries for marco
, providing the type-hint on how to map entries in the response. Set to String.class
here.
<2>
The response withAll()
will publish all elements in one single response.
<3>
And finally this response is provided the elements "polo", "yolo"
as the actual data to publish. The builder varags method, used here, is mostly for trying out Query/Response, or for static responses.
Again, the builder makes it really easy to create a responding service, without any special setup or complicated configurations.
Now if you run the application again, with the response component registered before the query publisher, it will publish the response.
$ mvn spring-boot:run
...
c.s.queryresponse.RabbitFacade : |<-- Published query: marco - (Body:'{}' MessageProperties [headers={x-qr-published=1589642489894}, replyTo=c77a8a1d-c959-4f2a-bd51-85b7e6b5b69b, contentType=application/json, contentLength=2, deliveryMode=NON_PERSISTENT, priority=0, deliveryTag=0])
c.s.queryresponse.Response : |--> Consumed query: marco
c.s.queryresponse.RabbitFacade : |<-- Published response: c77a8a1d-c959-4f2a-bd51-85b7e6b5b69b - (Body:'{"elements":["polo","yolo"]}' MessageProperties [headers={x-qr-published=1589642489941}, contentType=application/json, contentEncoding=UTF-8, contentLength=28, deliveryMode=NON_PERSISTENT, priority=0, deliveryTag=0])
c.s.queryresponse.Query : |--> Received response message: MessageProperties [headers={x-qr-published=1589642489941}, contentType=application/json, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=NON_PERSISTENT, priority=0, redelivered=false, receivedExchange=, receivedRoutingKey=c77a8a1d-c959-4f2a-bd51-85b7e6b5b69b, deliveryTag=1, consumerTag=amq.ctag-Q_ghWp4TWU9EYhi_rqErcg, consumerQueue=c77a8a1d-c959-4f2a-bd51-85b7e6b5b69b]
marco? polo
marco? yolo
Now you can see a full roundtrip of the query being published and consumed, and the response being published and also consumed. And the finished output is "polo" and "yolo" printed on STDOUT
.