How to delay the adoption of a particular dependency injection framework during the early development phase?

Viewed 34

Robert C. Martin in his book "Clean Architecture: A Craftsman's Guide to Software Structure and Design" mentions that a good architecture allows to delay decisions about details. One of those he mentions is:

It is not necessary to adopt a dependency injection framework early in development, because the high-level policy should not care how dependencies are resolved

What is a proper work methodology to achive that? How can you start developing in an efficient way without a particular dependency injection framework?

1 Answers

How can you start developing in an efficient way without a particular dependency injection framework?

The short answer is:

Just like you did before those DI frameworks exists!

Even those frameworks have a lot of benefits it's a quite good idea to start without them to write your code in a framework independent way. This addresses the code smell immobility.

Let's assume you want to setup a controller, a use case and repository.

 +------------+         +----------+        +------------+
 | Controller |  --->   | Use Case |  --->  | Repository |
 +------------+         +----------+        +------------+  

Just use standard constructors and setters.

 Repository repo = new RepositoryImpl();
 UseCase useCase = new UseCaseImpl(repo);
 Controller controller = new ControllerImpl(useCase);

Note: I don't prefer the Impl ending. It's just to distinguish between interface and implementation in this example.

The piece of code I showed above is just the same as what a DI framework does.

If you want to use a DI framework you can then wrap the code above in method that the DI framework uses. In java spring we would call such methods factory methods. E.g.

@Bean
public Controller controller(){
    Repository repo = new RepositoryImpl();
    UseCase useCase = new UseCaseImpl(repo);
    return new ControllerImpl(useCase);
}

Maybe the UseCase should be a singleton and therefore should be placed in a separate factory method.

@Bean
public UseCase useCase(DataSource ds){
    Repository repo = new RepositoryImpl(ds);
    repo.setCacheEnabled(true);
    return new UseCaseImpl(repo);
}

@Bean
public Controller controller(UseCase useCase){
    ControllerImpl controller = new ControllerImpl(useCase);
    SomeConfig c = ...;
    controller.setSomeConfig(c);
    return controller;
}

The main point is that the controller, use case and repository in my example only tell what they need and not where it come from. It's a good idea to separate the setup code or bootstrap code from the other.

Related