Home > database >  Conditionally enabling an external system integration in Spring Boot
Conditionally enabling an external system integration in Spring Boot

Time:10-05

I'm trying to implement support for several alternative integrations to external systems in a Spring Boot app. The idea is that a specific environment variable defines which integration is in use (there can only be one enabled at a time), and Spring Boot only initializes the components (@Configuration, @Service, @RestController etc.) specific to that integration. Is there a best practice approach for doing this?

Currently I'm thinking of something like this:

  • Create one @Configuration class per integration, annotate it with @ConditionalOnProperty. Read all relevant environment variables during app initialization, validate them, and prevent the app from starting if the configurations are invalid.
  • Annotate all other components related to that particular integration with @ConditionalOnBean, and make them dependent on the configuration bean being initialized.

Simplified example:

@Configuration
@ConditionalOnProperty(prefix = "integration", name = "name", havingValue = "FOO")
class FooConfig {
    // Read environment variables etc.
}

@Service
@ConditionalOnBean(FooConfig.class)
class FooService implements ExternalSystemIntegration {

    @Inject
    // Inject the configuration to get access to the relevant environment variables
    public FooService(FooConfig config, ...) { ... }

    @Override
    public void doIt() { ... }
}

Does this approach seem reasonable, or can you see some problems with it? One (small) downside is that it's not immediately clear which components are related to a specific integration (you need to find out which components depend on the configuration bean).

One alternative solution would be to create only one configuration class with a bunch of @Bean methods that would use the various @Conditional* annotations to control which components should be initialized. However, this would require manually feeding the dependencies to the created beans (as we use constructor injection), which I'd very much like to avoid. And I'm not sure if there's a good way to control which environment variables to read and validate during app bootstrapping with this solution.

CodePudding user response:

@Conditional... bean definitions are indeed the typical approach for this, and your approach sounds reasonable. If you're concerned that it might be hard to find which components depend on the configuration bean, you could:

  • declare your own @Conditional annotation, allowing you to find all matching components by scanning for the uses of that annotation, or
  • move all bean definitions sharing a condition into a configuration class with that condition, declaring the services using java based configuration. The disadvantage is that java based configuration makes constructor injection a bit verbose.
  • or you could move these into separate packages, and use conditional component scanning to only pick up the beans you want. However, that is only easy if the conditional packages are outside your normal package root, which is overkill in most cases.
  • Related