Skip to content

A Spring Boot sample app that uses RabbitMQ and shows how to test it.

License

Notifications You must be signed in to change notification settings

gazbert/spring-boot-rabbit-app

Repository files navigation

Spring Boot Rabbit App

Build Status Sonarcloud Status

What is this?

A Spring Boot sample app (and learning exercise!) that integrates RabbitMQ using Spring AMQP.

It could be used as boilerplate for developing microservices that produce and consume AMQP messages.

AMQP is an open standard wire specification for asynchronous message communication. AMQP is a platform-neutral binary protocol standard - libraries can be written in different programming languages and run on different environments.

AMQP is made up of exchanges, queues, and bindings. Clients publish a message to an AMQP exchange with a routing key. Queues are bound to an exchange using a routing key. The exchange then distributes the messages to the interested queues.

This sample app will demonstrate how to use and test 3 different exchange types:

  • Direct Exchange – routes messages to a queue by matching a complete routing key.
  • Fanout Exchange – routes messages to all the queues bound to it.
  • Topic Exchange – routes messages to multiple queues by matching a routing key to a pattern.

Install Rabbit

The fastest way to get up and running is to use the Docker image:

docker pull rabbitmq;
docker run -d -p 5672:5672 -p 15672:15672 --name my-rabbit rabbitmq:3-management

Once it's up and running, you can access the management UI using: http://localhost:15672

The default login credentials are guest/guest

The alternative is to install it (and Erlang) natively as per the official guide.

Build Guide

You'll need JDK 11 installed on your dev box.

You can use Gradle or Maven to build the app.

Both Gradle Wrapper and Maven Wrapper are included in the project.

Gradle

  1. From the project root, run ./gradlew build
  2. To generate the Javadoc, run ./gradlew javadoc and look in the ./build/docs/javadoc folder.

Maven

  1. From the project root, run ./mvnw clean install
  2. Take a look at the Javadoc in the ./target/apidocs folders after the build completes.

Checkstyle

The app uses the Google Style Guide which is enforced during both the Gradle and Maven build - see the build.gradle and pom.xml files respectively. The Checkstyle report locations are:

  • Gradle - ./build/reports/checkstyle/main.html
  • Maven - ./target/checkstyle-result.xml

Code Coverage

Code coverage is provided by JaCoCo and is enforced at build time. It's currently set to 80% line coverage. See the build files. The coverage report locations are:

  • Gradle - ./build/report/jacoco/test/html/index.html
  • Maven - ./target/jacoco-report/index.html

Code Quality

SpotBugs is run at build time. Any bugs found will fail the build. The bug report locations are:

  • Gradle - ./build/report/jacoco/test/html/index.html
  • Maven - ./target/spotbugsXml.xml

Tests

Unit tests are run as part of both Gradle and Maven builds.

JUnit 4 and Mockito is used to unit test the code.

The unit test report locations are:

  • Gradle - build/reports/tests/test/index.html
  • Maven - ./target/surefire-reports

The integration tests require a running instance of Rabbit - Testcontainers is used to achieve this. You'll need to stop any other instance of Rabbit that you have running, otherwise the tests will fail to due to port clashes.

The IT tests are located here.

The Spring profile must be set to 'integration-test' in the ./config/application.properties file:

spring.profiles.active=integration-test

To run the IT tests:

  • Gradle - ./gradlew integrationTests
  • Maven ./mvnw clean install -Pint

The IT report locations are:

  • Gradle - ./build/reports/tests/integrationTests/index.html
  • Maven - ./target/failsafe-reports

Configuration

The configuration is held in the ./config/application.properties file.

# Connection Factory details.
amqp.connection.hostname=localhost
amqp.connection.port=5672
amqp.connection.username=guest
amqp.connection.password=guest
        
# Set to false to stop error messages going back onto head of queue and looping forever!
spring.rabbitmq.listener.simple.default-requeue-rejected=false

# Uncomment for the appropriate ErrorHandlingApp demo being run.
# The Rabbit Docker container will need to be restarted after each change to remove the queues.
amqp.configuration.current=simple-dlq
#amqp.configuration.current=custom-dlx
#amqp.configuration.current=parking-lot-dlx

If the amqp.connection.* properties are not set, Spring AMQP will use defaults: $hostname, 5672 and guest/guest for credentials.

More details on the amqp.configuration.* properties can be found in the User Guide section below.

User Guide

There are 5 demos:

  1. A simple "Hello World!" app.
  2. A publish messages example using Direct, Fanout, and Topic exchanges.
  3. Error Handling using a simple Dead Letter Queue (DLQ).
  4. Error handling using a custom Dead Lead Exchange (DLX).
  5. Error handling using a Parking Lot Queue.

You'll need to restart the Rabbit Docker container after running each demo app in order to remove the queues.

docker container restart CONTAINER_ID

You can view the exchanges, queues, and messages in the Rabbit UI: http://localhost:15672

"Hello World!" app

The Hello World! app is the most basic example of a Spring Boot app using Spring AMQP - a good place to start.

From the the app from the project root folder using:

  • Gradle - ./gradlew bootRun
  • Maven - ./mvnw spring-boot:run

Publish Message app

The Publish Message app will publish a message to a Direct, Fanout, and Topic Exchange. Message consumers will read the message off the appropriate queue and log its contents.

From the project root folder using:

  • Gradle - ./gradlew bootRun -PmainClass=com.gazbert.rabbitsample.publish.PublishMessageApp
  • Maven - ./mvnw spring-boot:run -Dstart-class=com.gazbert.rabbitsample.publish.PublishMessageApp

Error Handling app

The Error Handling app can be run using 3 different configurations:

  1. Simple DLQ - a message is published to a Direct exchange. The Consumer reads the message off the queue and then throws a business exception. This exception is then routed to a DLQ where a Consumer reads the failed message and logs it.
  2. Custom DLX - a business exception is thrown as in the previous example and sent to a DLQ. This time, the Consumer attempts to re-send the message 3 times before giving up and discarding it.
  3. Parking Lot - as in the previous example, the failed message is re-sent 3 times, but instead of discarding it, the Consumer places the failed message on the Parking Lot Queue for manual investigation.

You'll need to update the ./config/application.properties file and set the amqp.configuration.current property to the example you want to run:

# Valid values are: simple-dlq, custom-dlx, parking-lot-dlx
amqp.configuration.current=simple-dlq

Run the app from the project root folder using:

  • Gradle - ./gradlew bootRun -PmainClass=com.gazbert.rabbitsample.errorhandling.ErrorHandlingApp
  • Maven - ./mvnw spring-boot:run -Dstart-class=com.gazbert.rabbitsample.errorhandling.ErrorHandlingApp

Logging

Logging for the app is provided by log4j. The log file is written to logs/app.log using a rolling policy. When a log file size reaches 100 MB or a new day is started, it is archived and a new log file is created.

The app will create up to 7 archives on the same day; these are stored in a directory based on the current year and month. Only the last 90 archives are kept. Each archive is compressed using gzip.

The logging level is set at info. You can change this default logging configuration in the config/log4j2.xml file.

Issue & Change Management

Issues and new features are managed using the project Issue Tracker - submit bugs here.

You are welcome to take on new features or fix bugs! See here for how to get involved.

Credits

This sample app is based off the excellent Spring AMQP tutorials by Baeldung.

References

  1. The Spring AMQP documentation.
  2. Routing Topologies and best practice.
  3. The official Spring AMQP Sample apps.