Dependency Injection: the pattern without the framework

It’s actually good!

Jean-Michel Fayard
Kt. Academy

--

Dependency Injection is a pattern, not any particular framework. You could even implement it manually, but that would be too much trouble.

Wait.. Why? How do we know that?

Update: added a paragraph on the distinction between Dependency Injection (a term I’ve been using loosely) and Service Locator (the alternative pattern I came to usually prefer).

Dependency Injection is a big topic in the Android world. Neglect it and you will soon feel the painful experience that all your code is closely tied to the android framework, and you cannot really test your app in a meaningful and actually useful way.

Dagger, a dependency injection framework originally from Square then remixed by Google, is the big framework of the android world (though not limited to it). Familiarity with dagger is assumed in this article.

Good resources on the topic include a presentation by Jake Wharton, a fragmented podcast episode, and that missing guide: how to use dagger2. I will steal examples found in the latter article since I’ve found it practical and on the point.

What the discussions on the topic have usually in common is that they insist that dependency injection is a general programming pattern, not any particular framework. You can do DI yourself… well, except you don’t because that would not be practical at all.

I wondered: is that really so? I thought I would give it a try, curious to see at which point I would hit a wall.

That didn’t happen.

Want to see how to do dependency injection with a couple of conventions, a few tricks, and otherwise plain normal languages constructs?

Let’s get started.

Business Logic / Component / Configuration modules

We will keep the basic architecture of dagger, described in the schema above.

Because we have a similar architecture, we sill start the exploration by showing how to migrate away from dagger.

Let’s assume we have dagger configuration modules set-up correctly. Dagger can provide us an implementation of the AppComponent and that’s all that we care about for now. This allows us to tackle directly the business logic. But first, we will refactor the Component.

Note: if you want to try out the code snippets above without having to configure a complete android project for that, use the following snippet that defines the needed types https://gist.github.com/jmfayard/5313e2fa8afdbba84bb94ea5a0a52792

Starting point

https://gist.github.com/jmfayard/9378a7ed21f61c40424442aa98732905

We start with a standard dagger project, except that I make the component easily accessible via a top-level function. The convention will be explained later, bear with me for now.

fun app(): AppComponent = ...

Constructor injection, not fields injection

https://gist.github.com/jmfayard/1ff428054248d3c2777f78f07da4dec3

There are two styles of DI you can do at this point. I’m firmly on the side that prefers constructor injection. Fields injection in my mind plays too well with what Israel Ferrer Camacho calls the “Burrito Design Pattern”, where you put too much stuff in god objects that belong to the framework. Constructor injection is clean and simple.

A Component of properties

Before we move on to the business logic, we will apply an important trick: using properties inside of getter methods in the component interface!

IntelliJ/Android Studio has just the right refactoring tool: convert function to property Let’s apply it to every getter method.

https://gist.github.com/jmfayard/e609e0c8c329b1ee19fab261de708c78

What happened here? For us, it will be important, but from the perspective of dagger: almost nothing. It sees your val property as a valid getter method. Compile the project again and it will continue to work like a charm.

Business Logic: constructor injection + default values

So we have a list of properties that can be injected. Here is how we can typically use them with the dagger approach of code generation:

https://gist.github.com/jmfayard/f1384464039b35e714fea72d750fb1d1

So we pass the properties in the constructor, marked it with the inject annotation, add a new getter method (or actually, a property) in the Component. To get a CatController, we ask the Component to provide it.

Can we achieve the same thing without relying on dagger?

Yes, we can with a simple pattern: we declare the required properties in the constructor like before, and we declare a default value coming from the same property from the component.

https://gist.github.com/jmfayard/184f7389ec851d6b448789ca9fd91fdb

I’ve been using this approach for months, alongside with dagger for building the AppComponent itself, and there are a lot of things I like about it:

  • the usage side is super straightforward. It’s just a standard call to the constructor, with the convention that you don’t pass any values for the optional arguments.
  • You don’t have to add a new property to the component
  • You have two possibilities to enable testing: either you change the dependency graph globally by creating aTestComponent implementing AppComponent
  • … or you pass directly any tests doubles you need directly in the constructor.
  • notice how you can mix freely injected fields ( catService) and normal constructor parameters ( catId )
  • injecting two properties of a different type is a non-brainer ( mainThread vs backgroundThread ). No need to use an additional annotation like Named
  • since we are using plain language constructs and not some extra-language annotation processing magic, your IDE(a) can help you in all the ways it usually helps you: autocompletion, jump to the definition of app(), see every implementation of a property, …

On the other hand, you will have to type a few more characters than we rely on Inject. I am fine with the kind of boilerplate that make things explicit and that works well with auto-completion, so it’s a valid tradeoff for me.

I’ve been using this pattern for months and won’t look back.

Recap

Here is how each three parts of our dependency injection currently compares to dagger

  • We keep dagger’s Componentabstraction with small but important tweaks: we use constructor injection, kotlin properties, and a top-level function to access it easily.
  • In the business logic, we roll our own convention for the constructor injection part
  • We have dagger implements the Component interface.

At that point, dagger is only an implementation detail. It implements the Component interface, and it does it well. But we could as well implement the Component interface directly.

There is a strong case to do just that when we start a new project.

Going From Zero To One

What if instead of starting with a project already using dagger, I am starting a fresh new project?

I have had my share of writing an app and then noticing too late that I can’t actually test it in a meaningful way. Never again! I want to have dependency injection from day 1. On the other hand, dagger is initially painful to setup. What if we could set up dagger later, but still use dependency injection?

Let say we are building the networking part. The most natural way to get started is something like that

https://gist.github.com/jmfayard/40b44ba26850d03986e5b56efe31c98f

Next we just need to make our interface ApiComponent useful. Let’s use the refactoring tool Pull Members Up.
Also, like before, we make the component accessible via a top-level function.

https://gist.github.com/jmfayard/b65b999a7729664365a11858e603c7eb

The IDE did almost all of the work here! Being lazy, this is something I enjoy a lot. We are ready to write testable production code.

Kotlin properties

Let’s dive deeper after this first success. How hard would it be to duplicate all the clever work that dagger is doing with its code generation in pure kotlin? The answer is that kotlin properties are very powerful

Singleton Provides fun retrofit(okHttpClient: OkHttpClient, moshi: Moshi)

This is how we provided retrofit in the ApiModule example. There is actually a lot going on here. In dagger terms, our ApiModule is an ObjectGraph that provides the type Retrofit, as a singleton, based on other methods that providesMoshi and OkHttpClient. The compiler and the IDE work hard to ensure we have no null pointer exceptions and don’t mess up the order in which we initialize the properties.

the compiler working hard for us

With kotlin properties, as one Perl motto says, simple things are easy, complicated things are possible.

At this point, we are stuck with a first world problem: should we use dagger to implement the Component or implement it ourselves?

To that question, my answer is clear: YES

Both methods work. Depending on the context, we can choose a different tradeoff. If the team knows dagger well, dagger is fine. If the team knows kotlin well, the direct approach is fine. Also, it’s a great learning experience.

I have happily used dagger for months. Recently I switched to implementing it directly. Switching is not hard. Same concepts, same direct acyclic graph of dependencies, just a different syntax.

The tipping point for me was that I modularized my app and was able to relegate all code generation tools except dagger to smaller modules. Removing kapt from the main module was a big win for having reliably fast incremental builds, build caches, network cache, … Also the superior IDE integration.

Bonus: what about java?

Having a project not 100% kotlin? No problem, the approach of relying on default argument works too. Just declare two constructors, one with all dependencies, and one where you pass the Component.

https://gist.github.com/jmfayard/8b79752a3b93a1e164ee3a98a88035d3

Bonus: going multi-modules

As I said, I was in the process of splitting my app into multiple smaller gradle modules. As it turns out, it’s pretty simple to do with this pattern.

The function fun app(): AppComponent is called so because it provides the properties that the :app gradle module needs to be injected.

If we have multiple modules like :app, :common we just need to repeat this pattern:

https://gist.github.com/jmfayard/e982331d4a8dbb8d122f424260c5e601

Bonus: Injecting Activities/Fragments

Until now, I have assumed that I deal with plain normal classes where I can use constructor injection. But what about the activities and fragments that belong to the framework? We have often at least (and should have often at most) 0 fragments and 1 activity.

There are actually quite a few different ways to simulate fields injection you may find useful

https://gist.github.com/jmfayard/eaf1c45b6b048b9eb51ece527192d7d1

Conclusion

If nothing else, trying to do manual Dependency Injection would have been a great learning experience. The first times I integrated dagger in my app, it felt overwhelming. Grasping at the same time the concepts and the syntax was hard. I have found it very helpful to dive into the core dependency injection pattern, which is actually simple despite being so useful. Once you get the pattern and have your graph of dependencies written down, setting up dagger is just a matter of syntax.

After months of playing with manual DI, I feel confident to say that it’s actually a fine alternative in many contexts.

It’s a superior experience when you start a new project and want to have DI as soon as possible. It‘s straightforward once you are used to it. The writing experience is superior, because all of the IDE integration that you are used to also works here, you don’t rely on features outside of the scope of the language. It plays nicely with build caches. It was a blessing for my build time because code generation is now confined to smaller modules.

Also, in the end, the concepts we are using are actually and intentionally not far from those from dagger. If at some point in the life of the project, it makes more sense to use the efficient and mature dagger framework, switching is not hard. Just a different syntax.

Update (27.02) : Dependency Injection vs Service Locator

I have been using loosely the term dependency injection in this article.

Astute readers have pointed out that the correct term for what I’m describing is a Service Locator. This is because my classes like CatController are reaching out externally (in my case via the default argument) to find their dependencies.

The minimum change I would have to make to move toward dependency injection would be this one below where the dependency to the locator itself is removed. From this contrived example one can begin to see the tradeoff. The CatController now knows nothing from the outside world. On the other hand the Component grows larger. There is a recursive aspect to it. Everything that merely *uses* an injected property should probably be produced by the component (DI framework) and scoped in a clever way (Our “scopes” were limited to: deliver a singleton or new instance)

https://gist.github.com/jmfayard/bfba725b36d99599ceeb0a9ee315114d

I had heard of this distinction before, but could not quite wrap my head around why it matters or wether I should care.

Before the discussion with my astute readers, I had a unclear pre-conception that the Service Locator pattern was somewhat inferior to the Dependency Injection pattern. In fact it’s quite similar, just has different thread-offs. Martin Fowler’s own take on this is that The choice between Dependency Injection and its Service Locator Alternative is less important than the principle of separating configuration from use.

The fundamental choice is between Service Locator and Dependency Injection. The first point is that both implementations provide the fundamental decoupling that’s missing in the naive example — in both cases application code is independent of the concrete implementation of the service interface. The important difference between the two patterns is about how that implementation is provided to the application class. With service locator the application class asks for it explicitly by a message to the locator. With injection there is no explicit request, the service appears in the application class — hence the inversion of control.

Inversion of control is a common feature of frameworks, but it’s something that comes at a price. It tends to be hard to understand and leads to problems when you are trying to debug. So on the whole I prefer to avoid it unless I need it. This isn’t to say it’s a bad thing, just that I think it needs to justify itself over the more straightforward alternative.

The key difference is that with a Service Locator every user of a service has a dependency to the locator. The locator can hide dependencies to other implementations, but you do need to see the locator. So the decision between locator and injector depends on whether that dependency is a problem (…)

Inversion of Control Containers and the Dependency Injection pattern

https://martinfowler.com/articles/injection.html

Taking a step back, what was I looking for? I wanted to decouple the core of my app from its boundaries (network, shared preferences, databases, android framework calls, …). I wanted to enable testing by easily replacing real service implementations with fakes one. I wanted to do this in a type-safe way.

The Service Locator pattern correctly implemented provides just that. It’s more straightforward because it still feels like regular code. The DI pattern on the other hand provides *more* than what I really needed. I can sense the beauty of it and how powerful it can be in some contextes, but it comes at a price. So my final realisation is that I would usually prefer a simpler Service Locator.

Links

If you like it, remember to clap. Note that if you hold the clap button, you can leave more claps!

To be up-to-date with great news on Kt. Academy, subscribe to the newsletter, observe Twitter and follow us on medium.

--

--