AddThis

Tuesday 30 October 2018

Creating a Custom Endpoint in Spring Boot Actuator

As we pointed out previously, we can create custom endpoints. However, Spring Boot 2 has redesigned the way to achieve this to support the new technology-agnostic paradigm.
Let’s create an Actuator endpoint to query, enable and disable feature flags in our application:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Component
@Endpoint(id = "features")
public class FeaturesEndpoint {
 
    private Map<String, Feature> features = new ConcurrentHashMap<>();
 
    @ReadOperation
    public Map<String, Feature> features() {
        return features;
    }
 
    @ReadOperation
    public Feature feature(@Selector String name) {
        return features.get(name);
    }
 
    @WriteOperation
    public void configureFeature(@Selector String name, Feature feature) {
        features.put(name, feature);
    }
 
    @DeleteOperation
    public void deleteFeature(@Selector String name) {
        features.remove(name);
    }
 
    public static class Feature {
        private Boolean enabled;
 
        // [...] getters and setters
    }
 
}
To get the endpoint, we need a bean. In our example, we’re using @Component for this. Also, we need to decorate this bean with @Endpoint.
The path of our endpoint is determined by the id parameter of @Endpoint, in our case, it’ll route requests to /actuator/features.
Once ready, we can start defining operations using:
  • @ReadOperation – it’ll map to HTTP GET
  • @WriteOperation – it’ll map to HTTP POST
  • @DeleteOperation – it’ll map to HTTP DELETE
When we run the application with the previous endpoint in our application, Spring Boot will register it.
A quick way to verify this would be checking the logs:
1
2
3
4
5
6
7
8
9
10
11
[...].WebFluxEndpointHandlerMapping: Mapped "{[/actuator/features/{name}],
  methods=[GET],
  produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}"
[...].WebFluxEndpointHandlerMapping : Mapped "{[/actuator/features],
  methods=[GET],
  produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}"
[...].WebFluxEndpointHandlerMapping : Mapped "{[/actuator/features/{name}],
  methods=[POST],
  consumes=[application/vnd.spring-boot.actuator.v2+json || application/json]}"
[...].WebFluxEndpointHandlerMapping : Mapped "{[/actuator/features/{name}],
  methods=[DELETE]}"[...]
In the previous logs, we can see how WebFlux is exposing our new endpoint. Would we switch to MVC, It’ll simply delegate on that technology without having to change any code.
Also, we have a few important considerations to keep in mind with this new approach:
  • There are no dependencies with MVC
  • All the metadata present as methods before (sensitive, enabled…) no longer exists. We can, however, enable or disable the endpoint using @Endpoint(id = “features”, enableByDefault = false)
  • Unlike in 1.x, there is no need to extend a given interface anymore
  • In contrast with the old Read/Write model, now we can define DELETE operations using @DeleteOperation

4.8. Extending Existing Endpoints

Let’s imagine we want to make sure the production instance of our application is never a SNAPSHOT version. We decided to do this by changing the HTTP status code of the Actuator endpoint that returns this information, i.e., /info. If our app happened to be a SNAPSHOT. We would get a different HTTP status code.
We can easily extend the behavior of a predefined endpoint using the @EndpointExtension annotations, or its more concrete specializations @EndpointWebExtension or @EndpointJmxExtension:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
@EndpointWebExtension(endpoint = InfoEndpoint.class)
public class InfoWebEndpointExtension {
 
    private InfoEndpoint delegate;
 
    // standard constructor
 
    @ReadOperation
    public WebEndpointResponse<Map> info() {
        Map<String, Object> info = this.delegate.info();
        Integer status = getStatus(info);
        return new WebEndpointResponse<>(info, status);
    }
 
    private Integer getStatus(Map<String, Object> info) {
        // return 5xx if this is a snapshot
        return 200;
    }
}

4.9. Enable All Endpoints

In order to access the actuator endpoints using HTTP, we need to both enable and expose them. By default, all endpoints but /shutdown are enabled.  Only the /health and /info endpoints are exposed by default.
We need to add the following configuration to expose all endpoints :
1
management.endpoints.web.exposure.include=*
To explicitly enable a specific endpoint (for example /shutdown), we use:
1
management.endpoint.shutdown.enabled=true
To expose all enabled endpoints except one (for example /loggers), we use:
1
2
management.endpoints.web.exposure.include=*
management.endpoints.web.exposure.exclude=loggers

5. Summary

In this article, we talked about Spring Boot Actuator. We started defining what Actuator means and what it does for us.
Next, we focused on Actuator for the current Spring Boot version, 1.x. discussing how to use it, tweak it an extend it.
Then, we discussed Actuator in Spring Boot 2. We focused on what’s new, and we took advantage of WebFlux to expose our endpoint.
Also, we talked about the important security changes that we can find in this new iteration. We discussed some popular endpoints and how they have changed as well.
Lastly, we demonstrated how to customize and extend Actuator.

No comments:

Post a Comment

100 AWS Services in Just One Line Each

  100 AWS Services in Just One Line Each Amazon EC2:  Virtual servers in the cloud. Amazon S3:  Scalable object storage service. Amazon RDS:...