Upcoming courses: | ||
---|---|---|
8th, 10th, 15th and 17th of February 2022 | Twice a week - 2 Tuesdays and 2 Thursdays | Register Now! |
We’ve talked about nested configurations. Now, let’s move on to using imported configurations in a smart way.
Let’s say in the main app (as in “production”), I have a whole lot of beans I need to inject.
The thing not to do (although you might be tempted) is dump them all into one big configuration class. It might feel like “just one more bean wouldn’t hurt”. But it will. Maybe not today, maybe not tomorrow. But soon, and for the rest of your project life.
We might end up with tens, even hundreds of beans, so using a big class may be convenient in answering “where do I put my next shiny new bean”, but at the same time, we probably want to segment them better .
Separate Configurations
One way to do this is by service or functionality: Each service has its own configuration. With micro-services this is crucial. When we update the service, we update the configuration too, and deploy them as a single unit.
There are some things that are more cross-cutting, and less service related, like general database connectivity initialization beans. We can put those in a separate configuration class. Then for the application we can use @ContextConfiguration to load the right configuration classes:
@ContextConfiguration(classes= {OurServiceConfiguration.class, DatabaseConfiguration.class, ...}) public class OurService { ... }
As alternative, we can also @Import at the configuration class level:
@Configuration @Import(DatabaseConfiguration.class) public class OurServiceConfiguration { ... }
And then at the service class:
@ContextConfiguration(OurServiceConfiguration.class) public class OurService { ... }
While both of these work, we conceal the fact that we’re using the DatabaseConfiguration in the service. This can come back to bite us later, as we’re carrying invisible baggage. So let’s stick with the first one.
And Testing Configurations?
When breaking down a full configuration into separate parts, we get a couple of advantages for testing:
- We load only what we need for the test, not the entire system configuration
- Run time (including load time) can seriously decrease
- Less things that can fail our tests
- Test setup becomes less of a hassle.
Here’s an alternative to the nested configuration example, only this time, the configuration is in a separate class.
@Configuration public class MainControllerConfiguration { @Bean public OurController controller() { return new OurController(); } } @Configuration @Import(MainControllerConfiguration.class) public class ControllerTestConfiguration { @Bean public OurDependency mockDependency() { OurDependency mock = Mockito.mock(OurDependency.class); when(mock.isActive()).thenReturn(true); return mock; } }
In this case, the MainControllerConfiguration class is in the production part of the application, and the ControllerTestConfiguration is in the test part (obviously). The latter imports the former, which can save us time in creating objects we already have defined, and don’t need to change.
So when we use the ControllerTestConfiguration in a test it looks like this:
@SpringBootTest @ContextConfiguration(ControllerTestConfiguration.class) public class OurControllerTests { @Autowired OurController controller; @Autowired OurDependency mockDependency; ... }
Notice that this way, we keep the link to the configuration through our ControllerTestConfiguration importer. Only it decides what to import or override. The test class is oblivious. This is quite cool.
So what’s “Overriding” a bean? That’s next.
0 Comments