Let’s take a small break from our Docker/Kubernetes series in which we try to optimize our beer-brewing and -selling throughput and focus a bit more on beer-brewing and -sales measurement.
Think about it! We need to keep our excel sheet managers busy, you don’t want those guys meddling with the overall brewing process!
In this blogpost we’re gonna build a Spring Boot REST Service from scratch using some Apache Camel REST DSL just for the fun of it. The gathering of metrics will be taken care of by Spring Boot’s metrics collector Micrometer. We’ll use Hawtio to browse the metrics of our service and, as an added bonus, we’ll also throw in a SpringFox Swagger UI.
As always the finished code can be found on GitHub here.
Micrometer
Let’s break down the final application starting with the most important part, i.e. Micrometer. Micrometer is Spring Boot’s preferred method of working with operational metrics. Our REST service is a simple service that exposes just one POST operation for processing a new Beer request. To keep it simple, the only thing the service does, is logging the Beer.
Beers can be of type lager or ale and with the help of Micrometer we’ll add counters that monitor the amount of lagers resp. ales posted. Working with Micrometer counters requires two steps. First you need to register the counter on startup and then you need to increment it wherever appropriate.
Maven dependencies
You need the following Maven dependency to make use of Micrometer:
<dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-core</artifactId> </dependency>
Registering Micrometer counters
The registration part is done in the @SpringBootApplication class:
@SpringBootApplication @EnableSwagger2 public class Application { @Autowired private MeterRegistry meterRegistry; public static void main(String[] args) { SpringApplication.run(Application.class, args); } @EventListener(ApplicationReadyEvent.class) public void doSomethingAfterStartup() { Counter.builder("beer.orders") .tag("type", "ale") .description("The number of orders ever placed for Ale beers") .register(meterRegistry); Counter.builder("beer.orders") .tag("type", "lager") .description("The number of orders ever placed for Lager beers") .register(meterRegistry); } }
The MeterRegistry instance to register the counters is injected into the Application class. After the ApplicationReadyEvent has fired, two counters are registered, i.e. the BeerOrders.type.ale counter for keeping track of ales posted and the BeerOrders.type.lager counter for posted lagers.
Incrementing Micrometer counters
Incrementing the counters is done in the Camel route that implements the POST operation. This metric logic is put in a Camel processor called BeerOrderProcessor:
@Component public class BeerOrderProcessor implements Processor { private final MeterRegistry meterRegistry; public BeerOrderProcessor(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; } @Override public void process(Exchange exchange) { Beer beer = exchange.getIn().getBody(Beer.class); switch (beer.getType()) { case LAGER: meterRegistry.counter("beer.orders", "type", "lager").increment(); break; case ALE: meterRegistry.counter("beer.orders", "type", "ale").increment(); break; default: } } }
Again the MeterRegistry is injected and is subsequently used to update one of the counters depending on the type of beer posted. To select the correct counter you need to supply its name (beer.orders) and optional tag(s) (eg. type=lager).
This is all there is to it! Exposing the actual metrics is done via the Hawtio console (see the Hawtio section in this blogpost).
For more information on Micrometer, check out its main documentation page Micrometer Application Monitoring and this Quick Guide to Micrometer.
Camel REST DSL
Exposing the service via REST is done with the help of the Camel REST DSL. For a good tutorial on setting up a REST service with Camel and Spring Boot check out this Apache Camel with Spring Boot guide.
Maven dependencies
You need the following Maven dependency to make use of the REST DSL:
<dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-servlet-starter</artifactId> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-jackson-starter</artifactId> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-swagger-java-starter</artifactId> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-spring-boot-starter</artifactId> </dependency>
The Swagger dependency takes care of automatically generating swagger api docs and exposing it via an endpoint.
REST configuration
The REST configuration can be done programmatically (see the aforementioned guide) but since we’re using Spring Boot, a cleaner way is to do it in the application.yml file:
camel: rest: context-path: /camel enable-cors: true api-context-path: /v2/api-docs api-property.api: title: Beer API with metrics version: v1
Most important properties here are the context-path – rest uris will start with it – and the api-context-path – this is where Camel will expose the Swagger docs.
REST DSL
First part of the Camel route is the REST DSL. It ends with a direct endpoint that delegates to the implementation:
rest("/api/").description("Beer Service") .id("api-route") .post("/beer") .produces(MediaType.APPLICATION_JSON) .consumes(MediaType.APPLICATION_JSON) .bindingMode(RestBindingMode.auto) .type(Beer.class) .enableCORS(true) .to("direct:remote-service");
The resource uri for the POST operation for the service according to our REST configuration and DSL will be camel/api/beer (remember that we set camel as the context path of the service).
The type operation here takes care of the unmarshalling of the input JSON to our Beer POJO.
Implementation
The second part of the route is the implementation of the POST operation.
from("direct:remote-service") .routeId("direct-route") .process(beerOrderProcessor) .log(INFO, "Beer ${body.name} of type ${body.type} posted") .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(201)) .to("mock:post-beer");
The route logs the beer, sends a 201 response and ends in a mock endpoint. The most important part is the beerOrderProcessor, which we already saw takes care of updating the metrics.
SpringFox
SpringFox is a great plugin for generating Swagger documentation and adding a GUI on top.
The Swagger doc in this case will be generated by the camel-swagger-java-starter but we still use SpringFox for generating the UI.
Maven dependencies
We need the following dependencies to add the SpringFox GUI functionality:
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>${springfox-swagger2.version}</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${springfox-swagger2.version}</version> </dependency>
Configuration
Enabling the SpringFox Swagger functionality is done by adding just one annotation to the main application:
@SpringBootApplication @EnableSwagger2 public class Application { …. }
And last but not least, bridging the UI and the Swagger docs is done in the application.yml file:
springfox: documentation: swagger: v2: path: /camel/v2/api-docs
After starting up the application, you will have the SpringFox UI available at http://localhost:8080/swagger-ui.html:
Hawtio
With the SpringFox UI we can fire calls at our Beer Service. The Micrometer metrics code takes care of updating our counters. Next up is the addition of the Hawtio console so we can actually verify that our metrics are being updated. As an added bonus, the Hawtio console also gives us some insight into the Camel route we put in our service. For more information check out this Getting Started guide.
Maven dependencies
You need this dependency for the Hawtio inclusion:
<dependency> <groupId>io.hawt</groupId> <artifactId>hawtio-springboot</artifactId> <version>${hawtio-springboot.version}</version> </dependency>
Configuration
Only one small other piece of configuration is needed for the Hawtio console. The property below, present in the application.yml file, disables the need for authentication when accessing the console.
hawtio: authenticationEnabled: false
JMX
We’ve been saving the best for last. This last piece of configuration glues it all together. Here we add the Spring Boot Actuator to the project and expose our metrics over JMX. After putting this last piece of configuration in place all the metrics will be available for viewing via the Hawtio Console. More information can be found on this Spring Boot reference doc.
Maven dependencies
We need the Spring Boot actuator dependency for exposing metrics over actuator endpoints. The micrometer-registry-jmx dependency will be the bridge between the Actuator endpoints and JMX.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-jmx</artifactId> </dependency>
The great thing about Micrometer is that it can publish its metrics to a lot of different monitoring systems. All that’s needed is putting the corresponding bridge dependency in place and exposing the right management endpoints (see next section). Publishing to Prometheus for example is as easy as publishing to JMX, like we are doing.
Configuration
The place where it really comes together is the application.yml file. Here we configure the Actuator and Micrometer magic:
management: endpoints: web: exposure: include: hawtio,jolokia, metrics server: port: 8912
For Hawtio to work and manage published JMX metrics you need to expose the hawtio and jolokia endpoints. The metrics endpoint will publish OS, JVM as well as application level metrics.
With these settings (also take note of the management.server.port property) the Hawtio console will be available at http://localhost:8912/hawtio after startup (the metrics endpoint will be available at http://localhost:8912/metrics by the way).
Testing
Before firing up the service and checking if everything works, let’s put some attention to unit testing this beasty. We’ll use JUnit version 5 for this. Check out A Guide to JUnit 5 for some great examples.
@SpringBootTest public class RestApiTest { @Autowired private ProducerTemplate producerTemplate; @Autowired private MeterRegistry meterRegistry; @EndpointInject(uri = "mock:post-beer") private MockEndpoint mockPostBeer; private final Beer lager = new Beer("Bavaria", BeerType.LAGER); private final Beer ale = new Beer("Bavaria", BeerType.ALE); @Test @DirtiesContext void whenPostDifferentBeerTypesExpectCorrectMetrics() throws InterruptedException { // given mockPostBeer.expectedMessageCount(5); // when producerTemplate.sendBody("direct:remote-service", lager); producerTemplate.sendBody("direct:remote-service", lager); producerTemplate.sendBody("direct:remote-service", ale); producerTemplate.sendBody("direct:remote-service", lager); producerTemplate.sendBody("direct:remote-service", ale); // then mockPostBeer.assertIsSatisfied(); assertEquals(3.0, meterRegistry.get("beer.orders").tag("type", "lager").counter().count()); assertEquals(2.0, meterRegistry.get("beer.orders").tag("type", "ale").counter().count()); } }
This unit test verifies whether the counters are updated to their correct values after posting some lagers and ales. Some pointers:
- A ProducerTemplate is autowired to post some beers to the camel route;
- The MeterRegistry is autowired to verify the counters;
- We inject the Camel MockEndpoint present at the end of the Camel route and use it to verify its interactions (every POST operation should end up with a message on the mock);
- With the help of the ProducerTemplate we POST 5 beers, three lagers and two ales (note that we can use POJOs here, cause when the route reaches the direct:remote-service endpoint, the unmarshalling from JSON to POJO is already done);
- We check if 5 messages have arrived on the mock endpoint and we assert that the lager counter has a value of 3 and the ale counter has a value of 2.
Letting the beers flow
Alright. Time to fire up the service and reap the rewards of our hard work. After the service is up and running, fire some beers into it using the SpringFox GUI and check the Hawtio console for the beerOrders metrics:
And there you go! You can clearly see both metrics are there and after POSTing 3 lager beers, you can see the count has been updated accordingly. That’s all there is to it.
Summary
In this blog post we dived into the Micrometer framework to help us publish some much needed metrics regarding our fast expanding beer brewery business. We published the metrics via JMX and used the Hawtio console for monitoring.
In this blogpost we only looked at micrometer counters. There are two other types of metrics available: gauges and timers. A gauge exposes the current value of an object (contrary to counters that can only go up, gauges can also go down). A timer is used to measure latencies or frequency of events.
That’s it for now. See you in our next post. Until then, stay safe and grab a beer!
References
Application code
Spring Boot metrics
- https://spring.io/blog/2018/03/16/micrometer-spring-boot-2-s-new-application-metrics-collector
- https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-metrics
Micrometer
Hawtio
Camel Spring Boot
JUnit 5
Spring Boot Actuator