Removing cross-cutting concerns with Micrometer and Spring AOP

Robin H
THG Tech Blog
Published in
7 min readJul 13, 2020

When you’re operating an enterprise system as a DevOps team, metrics are essential — whether it’s statistics for monitoring and alerting on system health, measuring response times for user experience, or even exposing domain metrics for making business decisions. Without them, you’re running blind.

In the THG Warehouse Management System team, as we deploy our software to new warehouse sites around the globe, we need a system that scales with us too. So what better way is there than to gather metrics consistently across all our sites than to write it into the code directly? In this article, I’ll explain the basics of what we did using Micrometer with Prometheus and show you how you can write noninvasive code for metrics gathering, or any cross-cutting concern, by taking advantage of some Aspect-Oriented Programming.

Prometheus is used as a data source for Grafana

Getting set up

If you use Spring Boot as we do for many of our microservices, and especially Sprint Boot 2, using Micrometer couldn’t be easier; it is already included in the actuator. If you are using 1.5, integrating Micrometer is still a piece of cake — just add a dependency and it’s done. After that, you can add a line of code wherever you like, and hey presto! You’ve got yourself a simple metric.

However, Micrometer is a ‘vendor-neutral facade’, not a one-stop-shop for metrics. If you’re not au fait with this lingo, it means Micrometer is an abstraction for talking to many different types of monitoring system and you will need one set up to make use of the data.

On start-up, Spring boot will configure a registry for each of the supported monitoring system implementations it finds on the classpath — this means to add a monitoring system all you’ll need to do is add a runtime dependency. The provided implementations are of the form micrometer-registry-{system}. For my team, using Prometheus, this means adding the following to our build.gradle file:

compile 'io.micrometer:micrometer-registry-prometheus:1.5.0'

Prometheus works on a pull model, which means it periodically makes an HTTP request for data from a service, known as ‘scraping’ the data. This means it needs an endpoint to scrape data from. To enable such an endpoint in Spring Boot 2, simply set a few application properties:

management.endpoint.metrics.enabled=true
management.endpoint.prometheus.enabled=true
management.metrics.export.prometheus.enabled=true
management.endpoints.web.exposure.include=prometheus

By doing this, you’ll add a new endpoint to your service, /actuator/prometheus. Once you hit the endpoint and can see it’s spitting out textual data, your service is successfully configured. By default, it gives out some handy statistics around HTTP responses and JVM stats, which is actually a nice set to get started with.

Gathering metrics

Now we’re set up, it’s time to think about gathering some bespoke metrics. Micrometer provides interfaces for all the basic types you’d expect, such as timers, gauges, counters, and more besides.

To demonstrate, consider a simple method for adding a product to a basket:

In the sample, first, a unit of stock is reserved and then added to the basket. Most of the time, this is fine as users are only presented with the option to add products that are in stock. However, occasionally an OutOfStockException is thrown when two users try and reserve the last unit of stock at the same time. If we want to track how frequently users are adding items to their basket and how often they’re failing to do so, you could do this quite simply with counters:

So far, so easy. The code is easy to understand and simple to write but really, this metrics code just doesn’t quite… belong. If you consider what the Netflix blog refers to as Hexagonal Architecture, what Uncle Bob calls Clean Architecture, or indeed any other system that divides software into layers, some code — like logging or metrics gathering — doesn’t fit in any of those layers but crosses all of them. These outlying bits of code are known as cross-cutting concerns.

Cross-cutting concerns aren’t functional behaviours of the code; when we added metrics gathering to the addToBasket method above we didn’t change anything from the user’s perspective. Should we write a test that asserts that the metric is gathered? Perhaps. Would you for a log statement? Perhaps not. Ideally, we’d remove these bits of code from the main execution flow and restore the original simplicity of the code. This should mean our unit tests can be concise and focused, adding to the overall maintainability of the code. For the simple example above, Micrometer provides us with a neat solution — annotations.

The Micrometer core library has two Annotation Types that fit our use case @Timed (for timing) and @Counted (for counting). We can simply tag our method with one of these and Micrometer will take care of the rest! The @Timed annotation is nicely documented, though some of the more snazzy features are not and the @Counted annotation isn’t documented at all (yet?). In truth, they’re actually very similar, as you might well expect. But we can access the library code, so let's go down the rabbit hole to figure out what we can do. (Prefer the blue pill? Skip to “Usage in Spring Boot”)

The interface with a hat

You can create your own Annotation Types in Java, they’re just like an interface with a hat on:

public @interface AnnotationName {}

In Micrometer the Counted Annotation Type is a wee bit more complicated, and this is a simplified version of it:

If you’ve not written an annotation type before, there are certainly a few bits here that you won’t be familiar with. First off, you’ll see the annotation definition itself is annotated — to address the first two briefly,Inherited means that any subclass including an annotated method will also be annotated and Target specifies precisely what can be annotated; a field, a parameter, a method, and so on (or a combination of any of those).

Less obviously, the Retention annotation dictates the ‘retention policy’ for the annotation. This is not normally something we need to worry about for our code, as this tells the compiler how long to retain the annotation for either SOURCE , CLASS, or RUNTIME. If set to SOURCE, the compiler will ignore the annotation, perhaps for use by other source code reading processes (e.g. javadoc). By default, retention is set to CLASS which means the annotation is written to the class file but not retained by the Java runtime, but in this case, it is important that the Retention Policy is RUNTIME which will allow access via reflection later on.

There are two other things to mention here that are specific to annotations. First, if you specify a default for a method, then this becomes optional when using the annotation — with this annotation you can choose either to record both successes and failures or if you override the value, to only record failures. Finally, if there is just only a single element named value, then the name can be omitted when using the annotation e.g.

@Counted("metric.name")

Rather than

@Counted(value = "metric.name", recordFailuresOnly = true)

Introducing… Aspect-Oriented Programming

This is all well and good, but to understand how the magic happens you need to know a bit about aspect-oriented programming. Rather than trying to define it myself, I’ll quote Wikipedia:

In computing, aspect-oriented programming (AOP) is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns. It does so by adding additional behavior to existing code (an advice) without modifying the code itself, instead separately specifying which code is modified via a “pointcut” specification.

The Wikipedia definition is especially useful because it maps directly to the AspectJ annotations that Spring AOP recognises — an @Aspect is made up of a @Pointcut (where to run the code) and whether to run it @Before, @After, or @Around the pointcut (the advice).

To achieve AOP, as in this example, by default Spring actually uses wrapper objects, known as proxies, around any object that is to be extended when it is constructing its object graph on application initialisation. All method calls to an object will go through the proxy and this means only methods can be acted on by an advice.

One big gotcha to be aware of is that if an object calls a method internally, self-references if you will, then this won’t go through the proxy — meaning the advice will not be applied.

Since Pointcuts can only work on methods, essentially it’s a method matching syntax. You can match a method in many different ways, by return type, method parameters, or name pattern-matching for example. To make a method easy to identify for a pointcut, you could add a prefix or suffix to the name, but this seems to go against all the teachings for good method naming, and following a convention seems tiresome and error-prone. This is why Annotation Types are such a good fit here, we can tag our method and it will simply work.

Piecing it all together

To demonstrate the concept, I’ve written a primitive version of the CountedAspect:

The “Aspect”

Having specified in the @Around annotation the pointcut to use, each method call that we want to count is intercepted and passed through this block of code. By using JoinPoint::proceed we catch an exception if the method were to throw it, count it, and rethrow to allow the normal execution flow to deal with it. If the method completes successfully then count the execution, unless we decided not to in the annotation.

Usage in Spring Boot

If we want to have spring handle the Aspect for us, we need to add it as a bean, in the configuration the same way you would for the timed annotation:

Finally, let's tag our method, ready to gather metrics

Simples!

And that’s it! Even though it’s complicated under the hood, the beauty of this solution is how easy it can be for the developer to use. But once you understand how to use them, annotation types and AOP can be a sharp knife in the developer toolbox.

--

--