Asynchronous communication by publish-subscribe / messaging systems is a very important element of high performance and resilient cloud applications. Instead of calling a client service and waiting for a response, you just send a message and can immediately continue. There are different levels of loose coupling. In the extreme case the sender of an event does not know or care how many other services (if any) subscribed to their event.
The goal of this exercise is that you learn how to send a message to inform other services about a specific event. Specifically, the task is to send an AMQP message to the RabbitMQ Message Queue Service (part of the standard CF backing services) whenever your Advertisement Service receives a request for a specific advertisement. This event (AMQP message) will then be picked up by a Statistics Service that counts how many times an advertisement was viewed.
Note: In this exercise we will only send messages. Receiving and processing is handled in the next excercise.
Continue with your solution of the last exercise. If this does not work, you can checkout the branch origin/solution-19-Transfer-CorrelationID
.
Add the spring-rabbit
dependency to your pom.xml
:
<!-- AMQP / RabbitMQ messaging -->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.0.1.RELEASE</version>
<exclusions>
<exclusion>
<!-- We need a more recent version of spring-context than the one included in spring-rabbit -->
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</exclusion>
</exclusions>
</dependency>
Note: After you've changed the Maven settings, don't forget to update your Eclipse project (Alt+F5
)!
Create a new class CloudRabbitConfig
in package com.sap.bulletinboard.ads.config
and copy the code from here.
- Create a new class
StatisticsServiceClient
in packagecom.sap.bulletinboard.ads.services
and annotate it with@Component
. Note: the classes we will be using in the following are fromorg.springframework.amqp.core
, so when you're fixing the import errors pick classes from this package. - In the constructor of
StatisticsServiceClient
you expect that theRabbitTemplate
gets injected. - Create a method
advertisementIsShown(long id)
. The responsibility of this method is to send out messages to the queue named "statistics.adIsShown":
public void advertisementIsShown(long id) {
rabbitTemplate.convertAndSend(null, "statistics.adIsShown", String.valueOf(id));
}
Note: In our setup, as we are not specifying any Exchange, we bind to the Direct Exchange so we bind to a Queue via a fixed routing key, which currently relates to the queue named "statistics.adIsShown".
Other implementation hints:
- Emit a log message whenever a message is sent.
- Think about providing the
Correlation-ID
as part of the message (messageProperties.setCorrelationId() via a MessagePostProcessor). Retrieve theCorrelation-ID
usingLogContext.getCorrelationId()
.
Inject the StatisticsServiceClient
into your AdvertisementController
class as part of the constructor and notify the Statistics Service whenever an individual advertisement is requested.
The virtual machine also provides a RabbitMQ service that you can use for local testing.
Based on the VCAP_SERVICES
environment variable the spring-cloud
connector instantiates as part of the CloudRabbitConfig
class the AmqpAdmin
and AmqpTemplate
instance that are used to communicate with the bound RabbitMQ service. Locally this environment variable needs to be set:
- In Eclipse, open the Tomcat server settings (by double-clicking on the server) and then open the launch configuration. In the Environment tab edit the
VCAP_SERVICES
variable. Replace the value with the following:
{"rabbitmq-lite":[{"credentials":{"hostname":"127.0.0.1","password":"guest","uri":"amqp://guest:[email protected]:5672","username":"guest"},"name":"rabbitmq-lite","label":"rabbitmq-lite","tags":["rabbitmq33","rabbitmq","amqp"]}],"postgresql-9.3":[{"name":"postgresql-lite","label":"postgresql-9.3","credentials":{"dbname":"test","hostname":"127.0.0.1","password":"test123!","port":"5432","uri":"postgres://testuser:test123!@localhost:5432/test","username":"testuser"},"tags":["relational","postgresql"],"plan":"free"}]}
- If you want to view / edit
VCAP_SERVICES
or JSON structures in general, you can e.g. use the Chrome JSON Editor plugin. - If you run the application from the command line, update your
localEnvironmentSetup
script:localEnvironmentSetup.sh
(localEnvironmentSetup.bat
) with the new value ofVCAP_SERVICES
.
You can run the application using Eclipse or on the command line:
$ source localEnvironmentSetup.sh
$ mvn tomcat7:run
- Send some GET-By-Id requests to the Advertisement Service like localhost:8080/api/v1/ads/1.
- Then, in the browser, visit the RabbitMQ Admin UI at http://localhost:15672/ (login with guest:guest).
Observe the information shown in the
Totals
view, and find out how many messages your application has sent.
In the current state the tests fail, as the statisticsServiceClient
bean can't be created, because the beans like the AMQPTemplate to be injected are undefined.
In previous exercises when we needed to connect to a (PostgreSQL) database, we used an embedded database (H2) in the tests.
In the case of RabbitMQ a similar approach is not easily possible.
Instead, we just provide mock instances of every queue-related bean that is defined in the CloudRabbitConfig
class.
As part of the src/test/java
source folder create a new class MockRabbitConfig
in the package com.sap.bulletinboard.ads.config
and copy the code from here. With that there is no need to mock the StatisticsServiceClient
.
After that the tests should run again.
A Message Queue is provided as backing service. There must be a RabbitMQ service instance on Cloud Foundry the application can connect to:
In the terminal, run the following command:
cf create-service rabbitmq v3.6-dev mq-bulletinboard
Note you can get the exact names of the available services and its plans with cf marketplace
.
In the manifest.yml
file, make sure the mq-bulletinboard
service is bound to your application:
services:
- mq-bulletinboard
With this the application should start when deployed in the cloud. Deploy your Advertisement Service and check in Kibana (via log messages) if the expected notifications are sent.
In order to test whether the messages in the queue can be processed by a consumer, you can deploy the Statistics Service as consuming application into your CF space. It is bound to the mq-bulletinboard
service and listens to it:
- Checkout the branch Statistics Service branch
- In order to deploy the statistics application enter on the command line:
# Ensure that you are in the project root e.g. ~/git/cloud-bulletinboard-ads
$ mvn clean verify
$ cf push -n bulletinboard-statistics-<<your user id>>
- Whenever you request an advertisment the Statistics Service should increase the counter for the same. To test this interaction you can call in the browser for example
[bulletinboard-statistics-<<<your user id>>>.cfapps.<<region>>.hana.ondemand.com/api/v1/statistics/1]
(https://bulletinboard-statistics-<<your user id>>.cfapps.<<region>>.hana.ondemand.com/api/v1/statistics/1
) - where "1" is the advertisment ID.
For HTTP/REST communication we used Hystrix so that failures of external dependencies can be mitigated. For message queue systems like RabbitMQ we do not notice failures of applications receiving the messages. However, the queue itself might fail for various reasons: e.g. the queue accepts no more messages.
Adapt your configuration so that the amqpTemplate.send
method is wrapped in a HystrixCommand
, similar as you've done in Exercise 17. Make sure to store the Correlation ID in a field, as the run
method will be called in a different thread, and use this field to initialize the log context using LogContext.initializeContext
.
Have a look at our sample branch exercise 20.
- RabbitMQ
- AMQP - Advanced Message Queuing Protocol
- Spring AMQP
- JSDoc AMQP Template (package org.springframework.amqp.core and interface AmqpTemplate)
-
© 2018 SAP SE