This projects illustrates how to use Spring boot on a simple use case.
It is based on 2 (micro)services:
-
Books
: an ebook store running on port8082
-
Numbers
: a backend service called by Books generating ISBN numbers. Runs on port8081
.
It is inspired from Antonio Goncalvez fascicule project.
During the demo, we will use Numbers "as is".
The focus will be put on Books
.
This demo has been developed with the following piece of software:
-
OpenJDK 11.0.10
-
Spring Boot 2.5.0
-
Docker 20.10.5
-
Docker-compoose 1.28.5
-
curl 7.64.1
-
jq 1.6 (possible alternative: python -m json.tool)
-
Gradle 7.0
This preparation should be done before running the live demo.
The infrastructure is made of:
-
PostgreSQL as database
-
Promotheus as backend for metrics
-
Jaeger as backend for OpenTracing.
To start it:
cd infrastructure docker-compose up -d
It may take a few minutes if the images have not been yet downloaded.
To check the infrastructure:
docker-compose ps
You should see the 3 containers running.
To stop it:
cd infrastructure docker-compose down
All script (*.sh) mentionned below are in the bin directory.
Tribute to Josh Long: see how beautiful the banner is!
Since we’re running a packaged JAR file, the code is in "default" mode.
Check that Books is ready:
curl -s http://localhost:8082/actuator/health/readiness | jq
We can see 2 live probes:
-
one relative to the database connection automatically provided by Spring
-
an other one related to an application specific maintenance flag.
By default the maintenance flag is set to false. That means that the service is ready.
There is an endpoint enabling to read and set that flag.
Let’s put the service in maintenance (setMaintenance.sh
):
curl -X 'PUT' \ 'http://localhost:8082/api/maintenance' \ -w "\n" \ -H 'Content-Type: text/plain' \ -d 'true' -v
We get an HTTP 204 answer (no-content).
We can check now that Books is no longer ready (checkMaintenance.sh
):
curl -s http://localhost:8082/q/health/ready | jq
Let’s inspect the code of MaintenanceController.retreiveInMaintenance()
to see how the HealthCheck probe is developped.
Let’s try to read a random book (randomBook.sh
):
curl -s \
-w "\n" \
'localhost:8082/api/books/random' | jq
We get an answer from Books:
{
"reason": "Service currently in maintenance"
}
Let’s browse the code of CheckMaintenanceFilter.java
and see how this answer is provided using a JAX-RS filter.
Let’s reopen the service (unsetMaintenance.sh
):
curl -X 'PUT' \
'http://localhost:8082/api/maintenance' \
-w "\n" \
-H 'Content-Type: text/plain' \
-d 'false' -v
We can now check now that Books is ready again (checkMaintenance.sh̀
):
curl -s http://localhost:8082/q/health/ready | jq
Let’s read a random book (randomBook.sh
):
curl -s -w "\n" 'localhost:8082/api/books/random' | jq
Browse the following source to see how its is implemented:
-
BookController.java
: Spring MVC, SpringDoc, Swagger, Actuator -
BookService.java
: Spring, Spring Data, OpenTracing -
BookRepository.java
: Spring Data
Let’s see the OpenAPI documentation (openapi.sh
):
curl -s -w "\n" localhost:8082/v3/api-docs | jq
Browse the following files to see how OpenAPI is coded:
OpenAPI/Swagger is enabled by default. You can disable it by applying this property
# Disable Swagger UI for the demo springdoc.api-docs.enabled=false
Some configuration parameters can be overriden at runtime. For instance:
java -Dserver.port=9080 -Dspringdoc.api-docs.enabled=false -jar build/libs/rest-book-0.0.1-SNAPSHOT.jar
However not all parameters can be overiden in such way. In particular, Swagger UI cannot be enabled/disabled at runtime.
Use your favorite browser and go to:
http://localhost:8082/swagger-ui.html
Have a quick test with GET API Books (list all books).
Numbers is called by Books on book creation (createBook.sh):
curl -s -w "\n" -X POST -d '{"title":"Practising Quarkus", "author":"Antonio Goncalves", "yearOfPublication":"2020"}' -H "Content-Type: application/json" localhost:8082/api/books -v
We’ve got a 202 status code (Created) and a link to the created resource provided with the Location header parameter.
Let’s read it:
curl -s -w "\n" localhost:8082/api/books/1 | jq
How does it work behind the scene? We make use of Spring RestTemplate
Browse BookService.java
, BookConfiguration.java
and application.yml
to see how it is coded:
-
application.yml
: a bit of configuration to define the target URL -
BookService.java
: the rest template is injected in the constructor -
BookConfiguration.java
: creates the restTemplate instance
So far, so good. But what if, Numbers is out of order? Let’s kill it … and try to create a book again.
Now we’ve got a 202 (Accepted) status code: the request has been accepted but the book has not been created, because no ISBN numbers have been provided.
What does it mean? In fact, we’ve entered a fallback mode: the book data have been stored in a file for later processing:
ls -l rest-book/book-*
Browse BookService.java
, BookConfiguration
and BookController.java
to see how FaultTolerance is coded:
-
CircuitBreakerFactory
usage onBookService.registerBook()
-
Resilience4JCircuitBreakerFactory
(global and specific) configuration onBookConfiguration
-
Catch
ApiCallTimeoutException
onGlobalExceptionHandler
Other features from FaulTolerance (not in the demo): Timeout, CircuitBreaker, Retry, BulkHead, Asynchronous.
Let’s switch to an important topic: observability and more specifically tracing.
Connect to the Jaeger GUI from your browser:
http://localhost:16686/
Jaeger is a distributed tracing system developped by Uber and donated to CNCF. It can be used for:
-
Distributed context propagation
-
Distributed transaction monitoring
-
Root cause analysis
-
Service dependency analysis
-
Performance / latency optimization
Let’s create a book again (createBook.sh):
curl -s -w "\n" -X POST -d '{"title":"Practising Quarkus", "author":"Antonio Goncalves", "yearOfPublication":"2020"}' -H "Content-Type: application/json" localhost:8082/api/books -v
Let’s search traces for Books. We can see how long has been spent in Books and Numbers.
By default, all REST endpoints are traced. No code is needed. You just have to add the Quarkus extension, to configure it and to run a backend system such as Jaeger (or Zipkin). It is also possible to annotate methods or classes with @Traced. Browse BookService.java.
Traces can also been enabled on JDBC at the risk of extreme verbosity.
OpenTracing must be configured in application.properties: it is possible to trace all or only parts of the requests.
Under the cover, context propagation is based on a specific HTTP header uber-trace-id.
Metrics is another aspect of observability.
It must be configured in application.yml
management: auditevents: enabled: true endpoint: shutdown: enabled: true health: enabled: true probes: enabled: true show-details: always prometheus: enabled: true metrics: enabled: true health: livenessstate: enabled: true readinessstate: enabled: true metrics: web: client: request: autotime: enabled: true
We can also gather rest endpoints metrics such as the time to respond.
Base metrics are about the JVM (classes, threads, gc)
curl -s http://localhost:8082/actuator/metrics/ curl -s http://localhost:8082/actuator/metrics/http.server.requests
Vendor metrics provides complementary technical metrics (cpu load, memory):
curl -s http://localhost:8082/actuator/metrics/system.cpu.usage curl -s http://localhost:8082/actuator/metrics/jvm.memory.used
It is also possible to add custom application metrics, such as a rest controller metrics.
To start aggregating them, you have to add the annotation @Timed
to the controller
Browse BookController.java
to see how it is implemented
curl -s http://localhost:8082/actuator/metrics/bookController
In contrast to OpenTracing, there is no default application metric. Methods have to be explicitelly annotated to generate metrics.
Curling metrics is limited to the current values, we have no historic. Let’s use Prometheus to collect metrics in a smart way. Prometheus is a metrics-based monitoring and alerting system, initially developed at SoundCloud and now hosted by the CNCF. It is internally based on Time Series Database.
Connect to the Prometheus GUI from your browser:
http://localhost:9090/graph
We can select a metric and do a graph with it. We can see different kinds of metrics:
-
counters: how much?
-
timers: how long?
Prometheus offers a basic GUI and it is recommended to use Grafana in production.
Browse BookController.java
to see how Metrics is coded.
Browse BookControllerIT.java
to see how to test using H2 in memory database and MockRestServiceServer