• Linas Naginionis
Software Craftsmanship

Dependency Injection Frameworks. Do we really need them?

Dependency Injection Frameworks. Do we really need them?

If you know me, you probably know that I’m not a huge fan of dependency injection frameworks. I’ve used them for a very long time but lately, especially after learning functional programming, I noticed that I’m not gaining anything useful from them. If you use Java there is ~99% probability that you are using Spring to manage your dependencies. But have you tried asking yourself: why I am doing this?

Recently I came up with a very good blog post which basically describes how to refactor your code so that you can inject your dependencies without usage of DI frameworks. I must confess that I completely agree with author’s opinion and how he has refactored his code. Having lambdas, there is no excuse for not doing this. This is basically the same approach we are doing in our company. We got rid of Spring completely and our code became more lightweight and even more testable. Also we don’t need to pack additional ~10-15 MB of unneeded dependencies.

But why managing dependencies by yourself is better than using DI container? Few reasons come to my mind:

  • Your code does not depend on some framework to wire everything up. Because it’s your code, you should know better how to do this.
  • Performance mostly is better because there are no proxies, heuristics involved when resolving your dependencies.
  • Your code becomes easier to test. No need for additional Spring, Mockito JUnit runners, etc. Also, instead of depending on some objects you should mostly depend on simple functions. Mocking becomes really trivial and straightforward.
  • No more meaningless stack traces (especially if you are using Spring)
  • It lets you to see your code smell faster. If you are having a hard time to manage your dependencies manually, it is a clear indication that there are issues with your code so you need to refactor it to make things easier. Usually DI containers hide this until the point when you need to refactor too much and we all know that refactoring should be done in small steps.

Just one notice. At first it could be quite hard to grasp why this approach is better, especially if you are doing basic OOP. When you know more functional programming, you will start to realize that there can be simpler answers or alternatives to many problems. The same goes with DI frameworks. You don’t need them (and mostly you don’t find them) in functional languages because language itself is designed to let you use simple approaches or patterns to accomplish things. But you need to learn the basic concepts first (monads, function composition, currying, partial application, lazy evaluation, statelessness, etc.) to start mastering them.

 

10 thoughts on “Dependency Injection Frameworks. Do we really need them?

  1. ggenikus

    Hi, Linas, thank you for the post.

    Could you show concert examples ?

    For example, here is Java + Spring DI example, could you show testable, FP version without DI (using any language you want)?

    Here i can easily test my service, mock external dependencies in test, also my service don’t depends on concrete implementation of UserDao or UserRegistrationLogger.

    interface UserDao{..}
    interface UserRegistrationLogger{..}

    @Component
    class MySqLUserDao implements UserDao{..}
    @Component
    class UserRegistrationLogger implements UserRegistrationLogger{..}

    @Service
    class UserServiceAwareOfDB implements UserService{
    private final UserDao userDao;
    private final UserRegistrationLogger registrationLogger;

    @Autowired
    public UserServiceAwareDB(final UserDao userDao, final UserRegistrationLogger registrationLogger){
    this. userDao = userDao;
    this. registrationLogger = registrationLogger;
    }

    @Override
    public User registerUser(User newUser){
    userDao.registerUser(newUser);
    registrationLogger.logRegistration(newUser);
    }

    }

    And test will look like:
    @RunWith(MockitoJunirRunner.class)
    UserServiceAwareOfDBShould{
    @Mock
    private UserDao userDao;
    @Mock
    private UserRegistrationLogger registrationLogger;

    @InjectiMocks
    private UserServiceAwareOfDB userService;

    @Test
    public void registerUserUsingDaoAndLogRegistration(){
    ..
    }
    }

    Thank you.

    1. Linas Post author

      Please read the blog post I linked in my post. There you’ll find how such code can be refactored. The basic idea is that instead of depending on interfaces or classes containing multiple methods, you should depend on only needed behavior, e.g. functions. In your example, instead of asking for UserDao or UserRegistrationLogger you just simply need to ask for behavior, f.i. your implementation could look like this (just a quick example, in production code it can be improved):

      public interface UserRegistration {
          static Consumer[User] registerUser(Consumer[User] userRegistrator, Consumer[User] userLogger) {
              return user -> {
                  userRegistrator.accept(user);
                  userLogger.accept(user);
              };
          }
      }
      

      And you test could look like this:

      @Test
          public void whenRegisterUserAlsoLogIt() throws Exception {
              UserRegistration.registerUser(user -> inMemoryDaoForTest.registerUser(user), Assert::assertNotNull)
                  .accept(new User("test user"));
          }
      

      Note that after the call to registerUser nothing will happen. Only when you supply User to Consumer, the registration will be performed. Also the returning Consumer[User] has already all his dependencies injected. Further code can use only this consumer and it won’t know anything about it’s dependencies.

      P.S. Somehow generics are messed up in comments so I changed them into scala style.

  2. Pingback: DI Frameworks. Do we really need them? – Debates

Leave a Reply

Your email address will not be published. Required fields are marked *

*