Pattern 11Replacing Dependency Injection

Intent

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.

Overview

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.

Functional Replacement

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.

In Scala

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.

In Clojure

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.

Sample Code: Favorite Videos

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.

Classic Java

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.

In Scala

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.

In Clojure

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.

Sample Code: Test Stubs

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.

Classic Java

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.

In Scala

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
 
}

In Clojure

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.

Related Patterns

Pattern 16, Function Builder

Pattern 21, Domain-Specific Language

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.172.200