3

I intend to write some HealtCheckContributors for a Spring Boot application using spring-boot-actuator. Hence, I implemented two of them. they are intended for checking the health of different apps, of course, but have a nearly identical structure, except the configuration properties, ...

SonarQube complains about that and I wonder if it is possible to have a single health check class but instantiated as many times as defined in application.properties. An example:

application.properties:

# actuator
app1.management.baseUrl=http://localhost:10000
app1.management.name=app1HealthCheckContributor
app2.management.basUrl=http://localhost:10001
app2.management.name=app2HealthCheckContributor

HealthCheckContributor for app1:

@Slf4j
@Component("xxx")
public class App1HealthCheckContributor extends AbstractHealthIndicator {

    private final App1Properties app1Properties;

    public App1HealthCheckContributor(final App1Properties app1Properties) {
        this.app1Properties = app1Properties;
    }

    @Override
    protected void doHealthCheck(Health.Builder builder) {...}
}

...and this code for each HealthCheckContributor only distinct in its appXProperties.

Isn't it possible to have some kind of base class like:

@Slf4j
@Component()
public class MyHealthCheckContributor extends AbstractHealthIndicator {

    private final MyProperties myProperties;

    public MyHealthCheckContributor(final MyProperties myProperties) {
        this.myProperties = myProperties;
    }

    @Override
    protected void doHealthCheck(Health.Builder builder) {...}
}

and let Spring Boot take care of instantiating two HealthCheckContributors (in our case App1HealthCheckContributor and App2HealthCheckContributor)? This would eliminate code duplication.

An example of the properties class file:

@Slf4j
@Data
@ConfigurationProperties(prefix = "app1.management")
public class App1Properties {
    private String baseUrl;
    private String ...;
}

How can I achieve this and how must an application.properties file looks like to achieve what I intend to do?

The final question: How to test multiple instance creation of a bean of one class filled with values from application.properties?

Jonas
  • 121,568
  • 97
  • 310
  • 388
du-it
  • 2,561
  • 8
  • 42
  • 80
  • Is the logic in `doHealthCheck` method of `App1HealthCheckContributor` and `MyHealthCheckContributor` exactly the same so that you can have a single class covering both use cases? – João Dias Oct 14 '21 at 17:09
  • Yes, the logic is equal. – du-it Oct 15 '21 at 23:37

1 Answers1

1

Assuming the code in doHealthCheck is exactly the same for all apps to be checked you could do the following.

You would start by creating a single health check class:

@Slf4j
public class AppHealthCheckContributor extends AbstractHealthIndicator {

    private final AppProperties appProperties;

    public App1HealthCheckContributor(final AppProperties appProperties) {
        this.appProperties = appProperties;
    }

    @Override
    protected void doHealthCheck(Health.Builder builder) {...}
}

And the properties model as follows:

@Slf4j
@Data
public class AppProperties {
    private String baseUrl;
    private String name;
}

This means that the configuration would be something like the following (in application.yml):

health-check:
  apps:
    - baseUrl: http://localhost:10000
      name: app1
    - baseUrl: http://localhost:10001
      name: app2

Finally, you would need to create a bean for each app and register them in the application context:

@Slf4j
@Data
@Configuration
@ConfigurationProperties(prefix = "health-check")
public class AllAppPropertiesConfiguration {
    private List<AppProperties> apps;

    @Autowired
    private GenericApplicationContext applicationContext;

    @PostConstruct
    fun init() {
        for (AppProperties app : apps) {
            applicationContext.registerBean(app.getName(), AppHealthCheckContributor.class, app);
        }
    }
}
João Dias
  • 16,277
  • 6
  • 33
  • 45
  • 1
    Works like a charm, João. How could I test that the contributors are generated and registered in the Spring Boot context? – du-it Oct 15 '21 at 23:41
  • You would need to add an `application-test.yml` configuration and use `test` profile in your `@SpringBootTest`. – João Dias Oct 16 '21 at 22:11
  • I already did but accessing the configuration always results in a NPE. – du-it Oct 17 '21 at 20:37
  • Then I would suggest you create an additional question and include all your code and tests. Share the link in a comment. Thanks! – João Dias Oct 17 '21 at 23:05
  • I did so. You can find the link at the end of my original question. Furthermore I found this article about avoiding @PostConstruct:https://levelup.gitconnected.com/stop-using-postconstruct-in-your-java-applications-2a66fb202cb8 What do you think about? – du-it Oct 18 '21 at 17:29
  • I get it and I agree if this is a regular Service or something similar. But this is a configuration class, you will never reuse it and most likely you will not test it directly. Nevertheless, you can always try the constructor approach. – João Dias Oct 18 '21 at 19:26
  • Instantiating the health check indicators this way prevent me from setting @ConditionalOnEnabledHealthIndicator annotation, right? I think I a allowed to put exactly only ONE indicator but not @ConditionalOnEnabledHealthIndicator(aaa,bbb,ccc). Hence, I can't dis-/enable the indicator in application.properties using management.endpoint.aaa.enabled=true/false ...or is there a possibility to do? – du-it Oct 21 '21 at 15:43
  • Maybe using @ConditionalOnEnabledHealthIndicator("app") with management.endpoint.app.enabled=true/false but this is an all or nothing approach and I can't disable selectively. – du-it Oct 21 '21 at 16:27
  • I would say it should work with the name you configure in your `application.yml`, but not sure to be honest. Never tried that. – João Dias Oct 21 '21 at 20:41
  • But WHICH name? I defined it as an array. I tried it using @ConditionalOnEnabledHealthIndicator(aaa,bbb,ccc) but this din't work. Maybe you are able to find a solution!? ;-) – du-it Oct 22 '21 at 06:45
  • `@ConditionalOnEnabledHealthIndicator` does not support multiple names. At least reading the documentation seems that way: "value - The name of the health indicator.". Maybe you can use multiple `@ConditionalOnEnabledHealthIndicator` but I am not really sure that Spring supports that. – João Dias Oct 22 '21 at 08:58
  • No, Spring complains about multiple usage of `@ConditionalOnEnabledHealthIndicator`. I tried already. Would be an improvement if Spring would support this. Seems as if this way of creating indicators was not considered or not wanted. – du-it Oct 22 '21 at 09:06