Imagine a scenario when we need to enable certain spring boot auto-configuration on a subset of environments only. The reason for that may be that i.e. our new feature will use new database (mongo) which hasn’t been setup on all environments yet for some reason, but we don’t want this issue to stop us from deploying new version of the application.
Not all spring boot auto-configurations support disabling them using property. We could try to exclude auto-configurations by declaring them in exclusions
property of @SpringBootApplication
and later import these auto-configurations using @Import
or @ImportAutoConfiguration
in profile specific configuration. Unfortunately this approach often does not work. Basically mixing auto-configuration and manual imports often leads to hard to diagnose configuration errors. Fortunately there is a better way which is purely based on auto-configuration.
Spring boot 2.4 introduced concept of 'profile groups' which allows expanding single profile into multiple sub-profiles. We can use profile groups to map a single profile identifying environment where application is running (dev
/ stage
/ prod
) to set of features which are enabled at each environment. In order to use profile groups we need to define spring.profiles.group
section in application.yml
https://gist.github.com/adgadev/1148317c3f486ef49847575b6eea8801
Such setup will result in having active profiles: dev
, bravo
, halo
on dev
environment. On prod
environment only prod
and bravo
profiles will be active.
In our case we will be building a feature based on mongoDb. When feature is disabled, the application should not require mongo database to exist nor utilize any mongo auto-configurations, since it’s the only feature using mongodb in our app. Apart from that all services, controllers and other application spring beans related with this feature should be created only when feature is enabled.
For start, we need to define annotation which will allow binding feature specific components & configurations with a dedicated spring profile.
https://gist.github.com/adgadev/03ea32b69a132e47571a6253c2b67f52
Next thing is to add auto-configuration’s condition which will allow enabling feature specific auto-configurations when feature profile is active:
https://gist.github.com/adgadev/5d34f1605ec708185bebbc7577ac063a
There are three mongo related auto-configurations used by our application. They need to be made conditional. Let’s create new auto-configuration classes which are subclasses of original auto-configurations and annotate them with @Conditional
utilizing feature condition:
https://gist.github.com/adgadev/48209579dd89b93e69b16718aa5b221b
https://gist.github.com/adgadev/5994adedaf5294b480c096e221e13031
https://gist.github.com/adgadev/dcc5dd01b0102d16504b715f35061015
Unfortunately this is not sufficient since spring boot conditional rules are not being inherited by subclasses. Hence, conditional rules has to be copied from MongoAutoConfiguration
, MongoDataAutoConfiguration
and MongoRepositoriesAutoConfiguration
to their subclasses.
The other thing is that dependencies declared in @AutoConfigureAfter
/ @AutoConfigureBefore
should refer to auto-configuration classes, not their subclasses. Otherwise, they won’t work. That’s why these annotations has to be copied from superclass to subclasses, but this time values inside annotations has to be replaced with corresponding Halo*AutoConfiguration
classes.
Other spring’s annotations used in auto-configuration subclasses like i.e. @Import
or @EnableConfigurationProperties
will work as if they were part of subclass auto-configuration, so there is no need to copy them from subclass.
After applying these changes we get following auto-configuration classes:
https://gist.github.com/adgadev/17a9b8baf7c0cc98f30fd99c5d409ccd
https://gist.github.com/adgadev/d67aa167151fc1887f15086933bfcc6e
https://gist.github.com/adgadev/ef869a40c2bfc6b6d9085d1a3eb9666c
New auto-configurations have to be registered in META-INF/spring.factories
file:
https://gist.github.com/adgadev/383267f2c3bc5691f350998516c058ca
Now let’s exclude original mongo auto-configurations:
https://gist.github.com/adgadev/bee259e095658fd535538b08c330325c
Having conditional mongo setup in place we can add simple mongo dependant feature called halo
. The feature consists of single document, mongo repository and a service. Repository and service beans are created only if the halo
feature is enabled.
https://gist.github.com/adgadev/36c86b9d52cf0745cbc6c28edf5063b2
https://gist.github.com/adgadev/7f05611ccb443cc9f8c0c1fd6ec6c0a0
https://gist.github.com/adgadev/d42d5b1bb8fd2522164876c22b9b5072
https://gist.github.com/adgadev/fd2aa86af5adafcd11c75a6cac58e4d6
There is also spring configuration which enables mongock framework for document migration and explicitly defines mongo repositories package:
https://gist.github.com/adgadev/caf8681d8c18e4d8f894c815ce5e66e2
Now let’s test how this feature works assuming it’s enabled only on dev and mongo database is present there only.
https://gist.github.com/adgadev/77d718f1f82a395c2fab998831d62dd8
Test is very simple. It creates mongo database using testcontainers, starts spring context and tests haloService
in such environment. The test is green when executed.
https://gist.github.com/adgadev/61c9ff64ecec82c8260fec96f64564ea
Test for prod env shows that application context starts successfully, despite the fact that there is no mongo database configured. None mongo or halo related spring been is constructed.
https://gist.github.com/adgadev/e349fdc683ac3cfa1b61cc93bfe1c519
The same test, but with halo
feature enabled fails on spring context creation, due to connectivity issues to mongo database when instantiating mongock’s beans.
https://gist.github.com/adgadev/e9846f5195bbc75d15e2b1fa2aed41c4
The full source code of the examples is available here.
Also published here.