Pattern 11 | Replacing Dependency Injection |
To compose objects together using an external configuration or code, rather than having an object instantiate its own dependencies—this allows us to easily inject different dependency implementations into an object and provides a centralized place to understand what dependencies a given object has.
Objects are the primary unit of composition in the object-oriented world. Dependency Injection is about composing graphs of objects together. In its simplest form, all that’s involved in Dependency Injection is to inject an object’s dependencies through a constructor or setter.
For instance, the following class outlines a movie service that’s capable of returning a user’s favorite movies. It depends on a favorites service to pull back a list of favorite movies and a movie DAO to fetch details about individual movies:
JavaExamples/src/main/java/com/mblinn/mbfpp/oo/di/MovieService.java | |
| package com.mblinn.mbfpp.oo.di; |
| public class MovieService { |
| |
| private MovieDao movieDao; |
| private FavoritesService favoritesService; |
| public MovieService(MovieDao movieDao, FavoritesService favoritesService){ |
| this.movieDao = movieDao; |
| this.favoritesService = favoritesService; |
| } |
| } |
Here we’re using classic, constructor-based Dependency Injection. When it’s constructed, the MovieService class needs to have its dependencies passed in. This can be done manually, but it’s generally done using a dependency-injection framework.
Dependency Injection has several benefits. It makes it easy to change the implementation for a given dependency, which is especially handy for swapping out a real dependency with a stub in a unit test.
With appropriate container support, dependency injection can also make it easier to declaratively specify the overall shape of a system, as each component has its dependencies injected into it in a configuration file or in a bit of configuration code.
There’s less of a need for a Dependency Injection–like pattern when programming in a more functional style. Functional programming naturally involves composing functions, as we’ve seen in patterns like Pattern 16, Function Builder. Since this involves composing functions much as Dependency Injection composes classes, we get some of the benefits for free just from functional composition.
However, simple functional composition doesn’t solve all of the problems Dependency Injection does. This is especially true in Scala because it’s a hybrid language, and larger bodies of code are generally organized into objects.
Classic Dependency Injection can be used in Scala. We can even use familiar Java frameworks like Spring or Guice. However, we can achieve many of the same goals without the need for any framework.
We’ll take a look at a Scala pattern called the Cake pattern. This pattern uses Scala traits and self-type annotations to accomplish the same sort of composition and structure that we get with Dependency Injection without the need for a container.
The unit of injection in Clojure is the function, since Clojure is not object oriented. For the most part, this means that the problems we solve with Dependency Injection in an object-oriented language don’t exist in Clojure, as we can naturally compose functions together.
However, one use for Dependency Injection does need a bit of special treatment in Clojure. To stub out functions for testing purposes, we can use a macro named with-redefs, which allows us to temporarily replace a function with a stub.
Let’s take a closer look at the sketch of a problem we saw in the Overview. There we created a movie service that allows us to do several movie-related actions. Each video is associated with a movie and needs to be decorated with details related to that movie, such as the movie’s title.
To accomplish this, we’ve got a top-level movie service that depends on a movie DAO to get movie details and on a favorites service to fetch favorites for a given user.
In Java, our top-level MovieService is sketched out in the following class. We use Dependency Injection to inject a FavoritesService and a MovieDao via a constructor:
JavaExamples/src/main/java/com/mblinn/mbfpp/oo/di/MovieService.java | |
| package com.mblinn.mbfpp.oo.di; |
| public class MovieService { |
| |
| private MovieDao movieDao; |
| private FavoritesService favoritesService; |
| public MovieService(MovieDao movieDao, FavoritesService favoritesService){ |
| this.movieDao = movieDao; |
| this.favoritesService = favoritesService; |
| } |
| } |
In a full program, we’d then use a framework to wire up MovieService’s dependencies. We have quite a few ways to do this, ranging from XML configuration files to Java configuration classes to annotations that automatically wire dependencies in.
All of these share one common trait: they need an external framework to be effective. Here we’ll examine Scala and Clojure options that have no such limitation.
Now we’ll take a look at an example of Scala’s Cake pattern. The rough idea is that we’ll encapsulate the dependencies we want to inject inside of top-level traits, which represent our injectable components. Instead of instantiating dependencies directly inside of the trait, we create abstract vals that will hold references to them when we wire everything up.
We’ll then use Scala’s self-type annotation and mixin inheritance to specify wiring in a typesafe manner. Finally, we use a simple Scala object as a component registry. We mix all of our dependencies into the container object and instantiate them, holding references to them in the abstract vals mentioned previously.
This approach has a few nice properties. As we mentioned before, it doesn’t require an outside container to use. In addition, wiring things up maintains static type safety.
Let’s start off with a look at the data we’ll be operating over. We’ve got three case classes, a Movie, a Video, and a DecoratedMovie, which represents a movie decorated with a video about it.
ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/di/ex1/Services.scala | |
| case class Movie(movieId: String, title: String) |
| case class Video(movieId: String) |
| case class DecoratedMovie(movie: Movie, video: Video) |
Now let’s define some traits as interfaces for our dependencies, FavoritesService and MovieDao. We’ll nest these traits inside of another set of traits that represent the injectable components. We’ll see why this is necessary later in the example.
ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/di/ex1/Services.scala | |
| trait MovieDaoComponent { |
| trait MovieDao { |
| def getMovie(id: String): Movie |
| } |
| } |
| |
| trait FavoritesServiceComponent { |
| trait FavoritesService { |
| def getFavoriteVideos(id: String): Vector[Video] |
| } |
| } |
Next up, we’ve got our implementations of the components introduced previously. Here we’ll stub out the MovieDao and FavoritesService to return static responses by implementing the interfaces. Note that we need to extend the component traits we’ve wrapped them in as well.
ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/di/ex1/Services.scala | |
| trait MovieDaoComponentImpl extends MovieDaoComponent { |
| class MovieDaoImpl extends MovieDao { |
| def getMovie(id: String): Movie = new Movie("42", "A Movie") |
| } |
| } |
| |
| trait FavoritesServiceComponentImpl extends FavoritesServiceComponent { |
| class FavoritesServiceImpl extends FavoritesService { |
| def getFavoriteVideos(id: String): Vector[Video] = Vector(new Video("1")) |
| } |
| } |
Now let’s take a look at MovieServiceImpl, which depends on the FavoritesService and MovieDao defined previously. This class implements a single method, getFavoriteDecoratedMovies, which takes a user ID and returns that user’s favorite movies decorated by a video of that movie.
The full code for MovieServiceImpl, wrapped up in a top-level MovieServiceComponentImpl trait, follows:
ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/di/ex1/Services.scala | |
| trait MovieServiceComponentImpl { |
| this: MovieDaoComponent with FavoritesServiceComponent => |
| |
| val favoritesService: FavoritesService |
| val movieDao: MovieDao |
| |
| class MovieServiceImpl { |
| def getFavoriteDecoratedMovies(userId: String): Vector[DecoratedMovie] = |
| for ( |
| favoriteVideo <- favoritesService.getFavoriteVideos(userId); |
| val movie = movieDao.getMovie(favoriteVideo.movieId) |
| ) yield DecoratedMovie(movie, favoriteVideo) |
| } |
| } |
Let’s take a closer look at this bit by bit. First we’ve got the self-type annotation on the top-level MovieServiceComponentImpl trait. This is part of the Scala magic that makes the Cake pattern typesafe.
| this: MovieDaoComponent with FavoritesServiceComponent => |
The self-type annotation ensures that whenever MovieServiceComponentImpl is mixed into an object or a class, this reference of that object has the type MovieDaoComponent with FavoritesServiceComponent. Put another way, it ensures that when the MovieServiceComponentImpl is mixed into something, MovieDaoComponent and FavoritesServiceComponent or one of their subtypes are as well.
Next up are the explicit vals that we’ll store references to our dependencies in:
| val favoritesService: FavoritesService |
| val movieDao: MovieDao |
These ensure that when we mix MovieServiceComponentImpl into our container object, we’ll need to assign to the abstract vals.
Finally, we’ve got the object that serves as our component registry, ComponentRegistry. The registry extends implementations of all of our dependencies and instantiates them, storing references to them in the abstract vals we previously defined:
ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/di/ex1/Services.scala | |
| object ComponentRegistry extends MovieServiceComponentImpl |
| with FavoritesServiceComponentImpl with MovieDaoComponentImpl { |
| val favoritesService = new FavoritesServiceImpl |
| val movieDao = new MovieDaoImpl |
| |
| val movieService = new MovieServiceImpl |
| } |
Now we can pull a full wired-up MovieService out of the registry when needed:
| scala> val movieService = ComponentRegistry.movieService |
| movieService: ... |
Earlier I claimed that this wiring preserves static type safety. Let’s explore what this means in greater detail. First, let’s take a look at what happens if we only extend MovieServiceComponentImpl itself in our object registry, as in the following code outline:
| object BrokenComponentRegistry extends MovieServiceComponentImpl { |
| |
| } |
This causes a compiler error, something like the following:
illegal inheritance; self-type com.mblinn.mbfpp.oo.di.ex1.Example.BrokenComponentRegistry.type does not conform to com.mblinn.mbfpp.oo.di.ex1.Example.MovieServiceComponentImpl’s selftype...
Here, the compiler is telling us that BrokenComponentRegistry doesn’t conform to the self-type we declared for MovieServiceComponentImpl, as we’re not also mixing in MovieDaoComponent and FavoritesServiceComponent.
We can fix that error by extending FavoritesServiceComponentImpl and MovieDaoComponentImpl, as we do in the following code:
| object BrokenComponentRegistry extends MovieServiceComponentImpl |
| with FavoritesServiceComponentImpl with MovieDaoComponentImpl { |
| |
| } |
However, this will get us another compiler error, which starts as follows:
object creation impossible, since: it has 2 unimplemented members...
This error is saying we haven’t implemented the favoritesService and movieDao members that MovieServiceComponentImpl requires us to.
Clojure doesn’t have a direct analog to Dependency Injection. Instead, we pass functions directly into other functions as needed. For instance, here we declare our get-movie and get-favorite-videos functions:
ClojureExamples/src/mbfpp/functional/di/examples.clj | |
| (defn get-movie [movie-id] |
| {:id "42" :title "A Movie"}) |
| |
| (defn get-favorite-videos [user-id] |
| [{:id "1"}]) |
Here we pass them into get-favorite-decorated-videos where they’re used:
ClojureExamples/src/mbfpp/functional/di/examples.clj | |
| (defn get-favorite-decorated-videos [user-id get-movie get-favorite-videos] |
| (for [video (get-favorite-videos user-id)] |
| {:movie (get-movie (:id video)) |
| :video video})) |
Another possibility is to use Pattern 16, Function Builder, to package up the dependent functions in a closure.
However, in Clojure, we generally only do this sort of direct injection when we want the user of the function to have control over the passed-in dependencies. We tend not to need it to define the overall shape of our programs.
Instead, programs in Clojure and other Lisps are generally organized as a series of layered, domain-specific languages. We’ll see an example of such in Pattern 21, Domain-Specific Language.
While Dependency Injection is largely concerned with the organization of programs as a whole, one specific area in which it’s especially helpful is injecting stubbed-out dependencies into tests.
In Java, we can just take our MovieService and manually inject stubs or mocks into it using constructor injection. Another option is to use the dependency injection container to instantiate a set of test dependencies.
The best approach depends on what sort of tests we’re currently writing. For unit tests, it’s generally simpler to just manually inject individual mocks. For larger integration-style tests, I prefer to go with the full-container approach.
With Scala’s Cake pattern, we can easily created mocked out versions of our dependencies. We do so in the following code snippet:
ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/di/ex1/Services.scala | |
| trait MovieDaoComponentTestImpl extends MovieDaoComponent { |
| class MovieDaoTestImpl extends MovieDao { |
| def getMovie(id: String): Movie = new Movie("43", "A Test Movie") |
| } |
| } |
| |
| trait FavoritesServiceComponentTestImpl extends FavoritesServiceComponent { |
| class FavoritesServiceTestImpl extends FavoritesService { |
| def getFavoriteVideos(id: String): Vector[Video] = Vector(new Video("2")) |
| } |
| } |
Now we only need to mix in and instantiate the stubbed components rather than the real ones, and then our test movie service is ready to use:
ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/di/ex1/Services.scala | |
| object TestComponentRegistery extends MovieServiceComponentImpl |
| with FavoritesServiceComponentTestImpl with MovieDaoComponentTestImpl { |
| val favoritesService = new FavoritesServiceTestImpl |
| val movieDao = new MovieDaoTestImpl |
| |
| val movieService = new MovieServiceImpl |
| } |
With our example Clojure code written as it is in the previous example, we only need to create test versions of our dependent functions and pass them into get-favorite-decorated-videos. We demonstrate this in the following code snippet:
ClojureExamples/src/mbfpp/functional/di/examples.clj | |
| (defn get-test-movie [movie-id] |
| {:id "43" :title "A Test Movie"}) |
| |
| (defn get-test-favorite-videos [user-id] |
| [{:id "2"}]) |
| => (get-favorite-decorated-videos "2" get-test-movie get-test-favorite-videos) |
| ({:movie {:title "A Test Movie", :id "43"}, :video {:id "2"}}) |
However, since we don’t always structure whole Clojure programs by passing in every dependency as a higher-order function, we often need an alternative method for stubbing out test dependencies. Let’s take a look at another version of get-favorite-decorated-videos that relies on its dependencies directly, rather than on having them passed in:
ClojureExamples/src/mbfpp/functional/di/examples.clj | |
| (defn get-favorite-decorated-videos-2 [user-id] |
| (for [video (get-favorite-videos user-id)] |
| {:movie (get-movie (:id video)) |
| :video video})) |
If we call get-favorite-decorated-videos-2, it’ll use its hard-coded dependencies:
| => (get-favorite-decorated-videos-2 "1") |
| ({:movie {:title "A Movie", :id "42"}, :video {:id "1"}}) |
We can use with-redefs to temporarily redefine those dependencies, as we demonstrate below:
| => (with-redefs |
| [get-favorite-videos get-test-favorite-videos |
| get-movie get-test-movie] |
| (doall (get-favorite-decorated-videos-2 "2"))) |
| ({:movie {:title "A Test Movie", :id "43"}, :video {:id "2"}}) |
Note that we wrapped our call to get-favorite-decorated-videos-2 in a call to doall. The doall form forces the lazy sequence produced by get-favorite-decorated-videos-2 to be realized.
We need to use it here because laziness and with-redefs have a subtle interaction that can be confusing. Without forcing the sequence to be realized, it won’t be fully realized until the REPL attempts to print it. By that time, the rebound function bindings will have reverted to their original bindings.
Clojure’s with-redefs is a blunt instrument. As you might guess, replacing function definitions on the fly can be quite dangerous, so this is best saved only for test code.
Pattern 16, Function Builder
Pattern 21, Domain-Specific Language
18.226.172.200