I strongly believe that every good software engineer should now the value coming from Dependency Inversion principle – it allows on writing better code which is easier to short – just shortly speaking. And in general, you can follow it without any IoC container, framework, implementation like Google Guice and Spring IoC. But as it’s harder, we tend to use the frameworks – but the help they provide goes sometimes too far …
Now, one of the ‘goodness’ of Spring Framework are stereotypes (Component, Repository, Service, Controller) which with connection of auto configuration can make life a
bit easier … or a bit harder in long-term.
Recently I was actually trying to convince myself to use it – and benefit from it in terms of efficiency of writing software. Going further, I defined Services, Components (but here, I’ve struggled with good meaning what should really be component vs service, where component tends to be more generic…) and some repositories. Then with usage of Spring Boot (@SpringBootApplication) and @Autowired I was able to auto magically wire all dependencies without using new keyword anywhere.
Even more, I’ve tried to use fancy @Autowired annotation defined directly on the private field, so I wouldn’t even specify constructor
or setter for it, less code = less maintenance! Was it really that good?
In reality, there is many issues I’ve started to have:
- first, if you use Ultimate IntelliJ version, you are fine (with fancy spring features), but if you use just community version – it struggles to recognize where you are using the code at all – or how it connects together
- it requires from someone reading the code to have good spring knowledge – it’s extreme coupling I would say to particular (external) framework – personally I believe it can be good but only if you have still a way to go out of it at some point – if you need it
- it’s hard to test … initially it’s hard to inject things (yep, there is Spring Test but it adds another complexity to your tests and the setup of tests). Things become even harder when you start to have more than one interface implementation and you need to orchestrate tests to make sure they know what to inject …
- dependencies are not explicitly set, is not that visible I should inject some contract implementation when it’s hidden besides of private @Autowired field …
What I did to fix that is – remove all stereotypes – it’s not using anymore auto wiring without specifying explicitly beans. Next, as I have already ‘configuration’ for spring stuff – in terms of simple application is just spring boot application class – I’ve moved all beans definition / creation in there (once it will grow, I can have separate Configuration keeping it).
All the private @Autowired fields are replaced with injection through constructor, making them explicit dependencies.
- I can test all the classes (previously components/services) without using spring, injecting stuff with mockito – tests are more clean and easier to understand now
- I keep all Spring configuration for beans in one place, and if I decide to move to Guice as example, that’s the only place I need to change
- For my integration tests, I can just use @Bean with @Primary, to overwrite any bean I would like to fake/mock/stub (like external rest service mock).