A friend of mine recently watched my Dependency Injection On-Ramp course on Pluralsight and sent me a few questions. In my experience, if one person has a question, there are probably other people with the same question. So, here are the questions and answers.
Note: these same questions/answers apply to my live presentation: Dependency Injection: A Practical Introduction.
Interfaces vs. Abstract Classes
Q: I see that interfaces are used with DI. Is there a reason abstract classes aren't used? Just wondering.
In the example, we create a repository interface so that we can add a layer of abstraction. Then we use constructor injection to get the repository into our view model class. Here's that code:
With this code, our view model does not need to know anything about the concrete repository types (so we can eliminate the coupling between the view model and those repositories). But there is nothing about Dependency Injection that limits us to using interfaces. In fact, in another part of the example, we inject a concrete class into our view:
Here we are injecting a concrete view model (MainWindowViewModel) into our view (MainWindow). For this, we do not get the same loose-coupling as we do with the repository example above. But Dependency Injection here still gives us some good advantages, namely adhering to the separation of concerns (SoC) and single responsibility principle (SRP). These principles often go hand in hand.
In this case, even though we need a compile time reference to the view model class, we have still freed the view from the responsibility of creating and managing the lifetime of the view model object. This means that creating the view model is not the responsibility of the view (separation of concerns) and the view can concentrate on what it does best -- providing the user interface for the application (it's single responsibility).
I guess I haven't answered the question yet (well, I've sort of answered it).
A: We can use whatever abstractions we like with Dependency Injection.
The type of abstraction is completely separate from dependency injection itself. That means we can use interfaces, abstract classes, or base classes. To make the decision, we'll just look at the pros and cons of using these types just like we do in our other code.
Specifically, interfaces are good where we do not have any shared code in the implementations (thus, no duplication). We can implement as many interfaces as we want, so we are not limited by single inheritance when we apply interfaces. And we're free to descend from another class if we want.
Abstract classes are good where we have shared code in the implementations. This lets us put the shared code into the abstract class and all of the child classes can use it (thus, no duplication). We are limited by single inheritance, so we cannot descend from an abstract class and another class at the same time.
If you want more information on how to make a decision between interfaces and abstract classes, you can check out my C# Interfaces course on Pluralsight, or reference the materials for my live presentation: IEnumerable, ISaveable, IDontGetIt: Understanding .NET Interfaces.
Client Applications vs. Server Applications
Q: All of your examples are with a client app. Can this be used server side, too? I'm assuming yes.
In the examples, we use Dependency Injection to break the coupling between the layers in a WPF (desktop client) application. Specifically, we break the coupling between our view model (presentation) and repository (data access) so that we can more easily extend and test our application.
And we can get these same benefits server-side as well. So, the answer is...
A: Yes, of course.
Another of my presentations uses an ASP.NET MVC project as an example: Shields Up! Defensive Coding in C#.
This example has a custom controller factory that uses Ninject. Here's that code:
The details aren't all that important here. What we're doing is creating a custom ASP.NET MVC controller factory. This class uses Ninject to inject the repositories for the objects in our application. This allows us to easily swap out the repository (similar to our WPF example above).
For this particular example, I'm showing the dangers of SQL injection. So I have a SQL repository that is vulnerable to SQL injection and another repository that uses parameterized SQL (and thus, is not vulnerable to this specific attack). Using the IRepository abstraction along with Ninject allows me to swap this out very quickly in my demo.
Okay, so you're probably saying that isn't a "real" server application (although, the code is running on the IIS server, not the client machine). So, here's another example. This is taken from a lab for an upcoming Education Day (more info on this soon).
This is an application that is designed to run inside an automated job. It loads a file, parses the data, and outputs any errors to a log file. In this case, we want to isolate the data parsing code to make it easier to unit test. So instead of having a logger hard-coded in the parser, we use constructor injection:
This lets us use the file logger when we're running this in production on our server, but it's easy to swap out the logger for a mock during our unit testing. (In fact, this particular lab is all about how to make code easier to test.)
So we can get the benefits from Dependency Injection (extensibility, testability, maintainability, late binding, parallel development) whether we are working with client applications or server-side code.
More Advanced Ninject
Q: How would you use the CachingRepository decorator with a container such as Ninject?
In the example code, we start out pretty simply: we manually wire our application pieces together in the composition root of our application. Here's the example using the repository that uses a WCF SOAP service:
This is pretty straight forward since we're using constructor injection all the way down. We create a repository, pass it to the view model, and then pass the view model to the view.
Using a container eliminates some of these steps. Rather than wiring everything together, we just create a binding between the abstraction (IPersonRepository) and the concrete type (PersonServiceRepository):
So we tell Ninject that if someone asks for an IPersonRepository, we want to return a PersonServiceRepository (and the InSingletonScope means we only want one of these that gets re-used throughout our application). We let the dependency injection container (Ninject) take care of figuring everything else out.
But in the example, we also created this really cool caching repository. This wraps any existing repository and adds a client-side cache to it. This was pretty easy to wire up manually:
We start with our concrete repository (PersonServiceRepository), pass that to our caching repository, then pass that to our view model, then pass that to our view. We just snap the pieces together in a different order.
But what about using a container? We can't just associate the IPersonRepository with the CachingPersonRepository because it also needs an IPersonRepository as a parameter. It seems like we'd end up in an endless loop.
A: Ninject (and other DI containers) provide lots of advanced features to handle complex situations.
When we configure our container, we just need to supply a little more information:
The first part of the configuration is the same (Bind...To...InSingletonScope), but then we add some further instructions. In this case, we specify that the CachingPersonRepository has a constructor parameter called "personRepository". When Ninject comes across this parameter, it should supply a PersonServiceRepository.
This has the same effect as our manual code: it takes a PersonServiceRepository and wraps it in the caching repository.
What this shows is that it pays to spend some time with a particular DI container. Learn the ins-and-outs of what is available. There are tons of options.
Wrap Up
Dependency Injection is still one of my favorite topics. It's a powerful tool that can give us some good advantages in many situations. Like every tool, it is not appropriate everywhere. But if we understand what it is good for, then we can make sure we use it in the right situations.
I'm always open to answering questions about the topics that I speak about. There is no such thing as a stupid question. This is how we learn.
Happy Coding!
Showing posts with label Constructor Injection. Show all posts
Showing posts with label Constructor Injection. Show all posts
Monday, April 21, 2014
Wednesday, January 29, 2014
Dependency Injection: The Property Injection Pattern
There are a variety of Dependency Injection patterns. Previously, we looked at the Service Locator pattern (or anti-pattern, depending on your point of view). We contrasted this with Constructor Injection to see the pros and cons of each approach.
In my presentation on Dependency Injection (and also my Pluralsight course), we cover Constructor Injection and Property Injection, and we see how Property Injection can be a good choice if we have a default value that we want to use at runtime, but we still want to be able to inject a different object for testing. But there are other ways to implement the Property Injection pattern.
Let's take a look at these approaches and see how they compare to Constructor Injection.
Property Injection with a Good Default
In the sample code, we look at using Property Injection as a way to facilitate unit testing. In this case, we're using it with a service. Here's that property code:
Our property is of type IPersonService -- this is the interface that specifies the service. When we run our application, we want to use a real WCF service (which is called "PersonServiceClient" -- technically, this is a proxy that points to the production service, but the proxy pattern lets us treat this as if it were the service itself). Because of the way we have this property set up, our repository will use our production WCF service by default (i.e., if we do nothing).
Runtime Behavior
So, if we call a method such as "ServiceProxy.GetPeople()", it will run the getter on the ServiceProxy property. The first time through, the backing field ("_serviceProxy") will be null. If it is null, then we new up an instance of our production WCF service ("PersonServiceClient") and assign it to the backing field. Then we return that value.
If we call "ServiceProxy.GetPeople()" a second time, the backing field will not be null (it will be already populated with our production service), and it will simply use the service proxy we already created.
And this is great at runtime. When we run our application, we want to use the production WCF service 100% of the time. But we are also leaving ourselves open to injecting that service through the property.
Testing Behavior
The issue is when we get to unit testing. We want to be able to test our repository class without needing to rely on a production WCF service. If the production service is down for some reason, then our tests will fail. But we don't want to test the service here; we want to test the repository class.
Because of the way we implemented our property, we can use Property Injection to inject a mock service into our tests. Here's what one of our tests looks like:
Immediately after creating the repository, we assign a new value to the ServiceProxy property -- before we call any methods on that service. This will set the ServiceProxy backing field to a mock or fake service that we can use for testing. And that's exactly what we do. Here's the configuration for the "_service" field that we use:
This creates a mock object (using Moq) that will behave the way we want it to for testing purposes.
Using Property Injection this way works really well when we have a default value that we use at runtime, but we want to swap out that value for testing purposes.
Property Injection with No Default
There is another way to implement the Property Injection pattern: by using an automatic property. Here's what that property would look like in our repository:
This property still has a setter, which means that we can swap out the value. But this time, we do not have a default value for the property. This means that we need to set the property somehow. We can do this by configuring our Dependency Injection container.
Injection with Unity
One of our samples from the session uses the Unity container (from the Microsoft Patterns & Practices team). Here's how we would need to expand the container configuration to inject this property (this is in the composition root of our application where we configure our container):
The syntax for Unity is a little cryptic. What we're saying here is that we want to add some configuration when the container resolves a "PersonServiceRepository" (which is our repository type). When Unity resolves this type, we would also like it to inject a value for the "ServiceProxy" property.
For this, Unity will go through its standard process. It will look at the ServiceProxy property, determine its type ("IPersonService"), and figure out how to resolve an object of that type. Now, we will need to give Unity a little more information for this. Here's one way of doing that:
In this code, we create an instance of the "PersonServiceClient" (that points to our production WCF service) and then register that instance with the Unity container. So, when we ask Unity to resolve an "IPersonService", it will provide us with the "serviceProxy" instance that we created.
And to take this a step further, when Unity is asked to inject the "ServiceProxy" property (which is of type "IPersonService"), it will inject the "serviceProxy" instance.
Our unit tests could remain unchanged -- we can manually set the "ServiceProxy" property in our tests (as we did above). Or, if we want to use a Unity container in our tests, we could configure the Unity container to use the mock service rather than the production service.
Alternative Injection
Other DI containers (such as MEF) may use different methodology to inject the property. I've worked with Prism (an application framework that also comes from the Microsoft Patterns & Practices team), and it provides an abstraction that allows us to configure property injection by using an attribute.
In that case, we just mark our ServiceProxy property with a "[Dependency]" attribute, and the framework ensures that the container injects that property.
A Big Drawback
There is a big drawback to implementing Property Injection this way. I managed to find this out the hard way when I was new to dependency injection. But it does make sense when you think about it.
When we do not supply a default value for the property, what happens if we try to include the following code?
This is the constructor for our repository. In the constructor, we use the ServiceProxy property to populate some data. We may want to do this if we want to initialize some data when our object gets created.
The problem is that this will result in a runtime error. (But it compiles just fine.)
Let's walk through what happens when our repository gets created (we'll assume its being created by the Unity container).
1. The Unity container creates an instance of the PersonServiceRepository. (This executes the constructor.)
2. The Unity container injects the ServiceProxy property based on the "RegisterType" configuration that we saw above.
Do you see the problem now? Then constructor executes *before* the property is injected. (This is also the case when using the "[Dependency]" attribute with Prism.) This results in a null-reference exception when we try to access the ServiceProxy property in the constructor.
Mitigating the Risks
We've seen the potential risks with a "no default" implementation of Property Injection. How do we get around this?
Well, we'll go back to our recommendations from the Dependency Injection session: We should favor using Constructor Injection for items that our object requires but do not have good default values. So, instead of using Property Injection in this case, we can move the dependency into the constructor as a parameter.
So, our constructor would look like this:
This lets us safely use the "ServiceProxy" property in our constructor.
Another option is to move this initialization code out of the constructor. We generally want our constructors to run quickly, and this means avoiding network or database calls during construction. We can look at lazy loading our data when we actually use it. Then we can safely use Property Injection in our class.
Wrap Up
After looking at this, we may think that Property Injection should be avoided. But Property Injection is still a good dependency injection pattern. We just need to be aware of the consequences of the pattern before we implement it in our code (this is true of all design patterns). And this leads us to the recommendations as we approach dependency injection.
Use Property Injection when we have a default value that we want to swap out for testing. But favor Constructor Injection as much as possible. Constructor Injection has the benefit of keeping dependencies obvious (as we saw when we looked at the Service Locator).
Dependency Injection helps us create loosely coupled code that facilitates maintainability, extensibility, and testability. The better we understand the pros and cons of each of the dependency injection patterns, the better the results will be in our applications.
Happy Coding!
In my presentation on Dependency Injection (and also my Pluralsight course), we cover Constructor Injection and Property Injection, and we see how Property Injection can be a good choice if we have a default value that we want to use at runtime, but we still want to be able to inject a different object for testing. But there are other ways to implement the Property Injection pattern.
Let's take a look at these approaches and see how they compare to Constructor Injection.
Property Injection with a Good Default
In the sample code, we look at using Property Injection as a way to facilitate unit testing. In this case, we're using it with a service. Here's that property code:
Our property is of type IPersonService -- this is the interface that specifies the service. When we run our application, we want to use a real WCF service (which is called "PersonServiceClient" -- technically, this is a proxy that points to the production service, but the proxy pattern lets us treat this as if it were the service itself). Because of the way we have this property set up, our repository will use our production WCF service by default (i.e., if we do nothing).
Runtime Behavior
So, if we call a method such as "ServiceProxy.GetPeople()", it will run the getter on the ServiceProxy property. The first time through, the backing field ("_serviceProxy") will be null. If it is null, then we new up an instance of our production WCF service ("PersonServiceClient") and assign it to the backing field. Then we return that value.
If we call "ServiceProxy.GetPeople()" a second time, the backing field will not be null (it will be already populated with our production service), and it will simply use the service proxy we already created.
And this is great at runtime. When we run our application, we want to use the production WCF service 100% of the time. But we are also leaving ourselves open to injecting that service through the property.
Testing Behavior
The issue is when we get to unit testing. We want to be able to test our repository class without needing to rely on a production WCF service. If the production service is down for some reason, then our tests will fail. But we don't want to test the service here; we want to test the repository class.
Because of the way we implemented our property, we can use Property Injection to inject a mock service into our tests. Here's what one of our tests looks like:
Immediately after creating the repository, we assign a new value to the ServiceProxy property -- before we call any methods on that service. This will set the ServiceProxy backing field to a mock or fake service that we can use for testing. And that's exactly what we do. Here's the configuration for the "_service" field that we use:
This creates a mock object (using Moq) that will behave the way we want it to for testing purposes.
Using Property Injection this way works really well when we have a default value that we use at runtime, but we want to swap out that value for testing purposes.
Property Injection with No Default
There is another way to implement the Property Injection pattern: by using an automatic property. Here's what that property would look like in our repository:
This property still has a setter, which means that we can swap out the value. But this time, we do not have a default value for the property. This means that we need to set the property somehow. We can do this by configuring our Dependency Injection container.
Injection with Unity
One of our samples from the session uses the Unity container (from the Microsoft Patterns & Practices team). Here's how we would need to expand the container configuration to inject this property (this is in the composition root of our application where we configure our container):
The syntax for Unity is a little cryptic. What we're saying here is that we want to add some configuration when the container resolves a "PersonServiceRepository" (which is our repository type). When Unity resolves this type, we would also like it to inject a value for the "ServiceProxy" property.
For this, Unity will go through its standard process. It will look at the ServiceProxy property, determine its type ("IPersonService"), and figure out how to resolve an object of that type. Now, we will need to give Unity a little more information for this. Here's one way of doing that:
In this code, we create an instance of the "PersonServiceClient" (that points to our production WCF service) and then register that instance with the Unity container. So, when we ask Unity to resolve an "IPersonService", it will provide us with the "serviceProxy" instance that we created.
And to take this a step further, when Unity is asked to inject the "ServiceProxy" property (which is of type "IPersonService"), it will inject the "serviceProxy" instance.
Our unit tests could remain unchanged -- we can manually set the "ServiceProxy" property in our tests (as we did above). Or, if we want to use a Unity container in our tests, we could configure the Unity container to use the mock service rather than the production service.
Alternative Injection
Other DI containers (such as MEF) may use different methodology to inject the property. I've worked with Prism (an application framework that also comes from the Microsoft Patterns & Practices team), and it provides an abstraction that allows us to configure property injection by using an attribute.
In that case, we just mark our ServiceProxy property with a "[Dependency]" attribute, and the framework ensures that the container injects that property.
A Big Drawback
There is a big drawback to implementing Property Injection this way. I managed to find this out the hard way when I was new to dependency injection. But it does make sense when you think about it.
When we do not supply a default value for the property, what happens if we try to include the following code?
This is the constructor for our repository. In the constructor, we use the ServiceProxy property to populate some data. We may want to do this if we want to initialize some data when our object gets created.
The problem is that this will result in a runtime error. (But it compiles just fine.)
Let's walk through what happens when our repository gets created (we'll assume its being created by the Unity container).
1. The Unity container creates an instance of the PersonServiceRepository. (This executes the constructor.)
2. The Unity container injects the ServiceProxy property based on the "RegisterType" configuration that we saw above.
Do you see the problem now? Then constructor executes *before* the property is injected. (This is also the case when using the "[Dependency]" attribute with Prism.) This results in a null-reference exception when we try to access the ServiceProxy property in the constructor.
Mitigating the Risks
We've seen the potential risks with a "no default" implementation of Property Injection. How do we get around this?
Well, we'll go back to our recommendations from the Dependency Injection session: We should favor using Constructor Injection for items that our object requires but do not have good default values. So, instead of using Property Injection in this case, we can move the dependency into the constructor as a parameter.
So, our constructor would look like this:
This lets us safely use the "ServiceProxy" property in our constructor.
Another option is to move this initialization code out of the constructor. We generally want our constructors to run quickly, and this means avoiding network or database calls during construction. We can look at lazy loading our data when we actually use it. Then we can safely use Property Injection in our class.
Wrap Up
After looking at this, we may think that Property Injection should be avoided. But Property Injection is still a good dependency injection pattern. We just need to be aware of the consequences of the pattern before we implement it in our code (this is true of all design patterns). And this leads us to the recommendations as we approach dependency injection.
Use Property Injection when we have a default value that we want to swap out for testing. But favor Constructor Injection as much as possible. Constructor Injection has the benefit of keeping dependencies obvious (as we saw when we looked at the Service Locator).
Dependency Injection helps us create loosely coupled code that facilitates maintainability, extensibility, and testability. The better we understand the pros and cons of each of the dependency injection patterns, the better the results will be in our applications.
Happy Coding!
Tuesday, April 16, 2013
Dependency Injection: The Service Locator Pattern
The Service Locator pattern (or anti-pattern) is one of the many Dependency Injection patterns that allow us to create loosely-coupled code. I mentioned this pattern just a bit in my review of Mark Seemann's excellent book Dependency Injection in .NET (Jeremy's Book Review).
Seemann refers to the Service Locator as an anti-pattern -- meaning that we probably don't want to use it in most cases because there are better patterns out there. But he also notes that he used the pattern for many years before coming to grips with its shortcomings.
As mentioned in the book review, I have also used the Service Locator, and I was a little surprised when it was referred to as an anti-pattern. But in reading Seemann's description of the shortcomings, I had no choice but to agree. I had actually come across these shortcomings in my own code, but in that environment, it made sense to continue to use the pattern.
So let's compare the Service Locator to one of the other DI patterns (specifically, the Constructor Injection pattern). This will point out the problems that I ran across -- which also happen to be the problems that Seemann mentions in his book.
Constructor Injection
We'll start by taking a look at the Constructor Injection pattern. For a detailed description of Constructor Injection, refer to my presentation "Dependency Injection: A Practical Introduction".
The basics of Constructor Injection is that we pass the dependencies that a class needs as parameters in the constructor. Here's an example:
In this constructor, we are injecting an IPersonService and a CatalogOrder. The constructor then assigns these to local fields that can be used by the class. Whatever creates this object is responsible for supplying these dependencies. Generally, this is done in the composition root and often involves a Dependency Injection container.
Now, let's compare this to the Service Locator.
Service Locator
The Service Locator varies a bit from Constructor Injection. As mentioned above, with Constructor Injection, something else is responsible for resolving the dependencies that are needed by the class.
In contrast, when using the Service Locator, the class itself is responsible for resolving its dependencies by asking for them from the Service Locator. This can be a DI container or some other object or method that can return dependencies. In our sample, we'll use a DI container (Unity).
Here's a sample constructor:
Notice that the constructor is passed the entire DI container (although there are other ways to get the service locator into the object). Then the object uses the service locator to pick out its own dependencies. This takes the control away from the composition root and gives it to the object itself.
Note that just because you are using a DI container doesn't mean that you are using the Service Locator pattern. The Service Locator is defined by how the container is used (i.e., whether the object is resolving its own dependencies).
On the surface the Service Locator appears to hit all of the points of Dependency Injection: it allows for extensibility; it allows for the swapping out of test doubles; it allows for late binding. This is why it is considered by many to be a valid DI pattern.
But it has a major shortcoming: It hides the dependencies that are being used.
This might not seem like a big problem, but let's take a look at some unit tests to show the shortcoming in action.
Initial Unit Tests
We'll start by unit testing the constructor that uses Constructor Injection. Here's our test:
Notice here that when we instantiate our CatalogViewModel, we must provide the both the IPersonService (_personService) and the CatalogOrder (_currentOrder). If we didn't provide both of these parameters, the test code would not build. As a side note, both of these dependencies (_personService and _currentOrder) are mock objects that are created in the test setup (not shown).
This test builds and passes with our current code.
Now let's look at the same test for the constructor that uses the Service Locator:
In this test, we must pass in a populated Unity container (called "_locator" in this case). As noted in the comments, our "_locator" has both an IPersonService and a CurrentOrder registered in its catalog (these are added in the test setup).
This test builds and passes with our current code.
The Problem with Hiding Dependencies
With Constructor Injection, we can easily see that we have 2 dependencies just by looking at the constructor. However, with the Service Locator, we don't have visibility to the dependencies from the outside (meaning, looking at the public API signatures of the class).
Let's see what happens when we add a 3rd dependency. Our Constructor Injection implementation now looks like this:
The first thing that happens is that we get a build failure of our unit test. Note the squigglies below:
Build failures are good. They give you immediate feedback that something is wrong. In this case, we can see that we have a missing dependency since the constructor wants a 3rd parameter. We would see this same error throughout our code base wherever we try to construct a CatalogViewModel. And we know exactly what to fix.
But what about the Service Locator. Let's add another dependency:
If we rebuild our application, everything builds fine -- including the existing unit test for the constructor that uses the Service Locator. The problem is when we run the tests.
Since our tests do not know about the 3rd dependency, the test setup does not load it into the Unity container. So, this test will throw an exception (when it tries to resolve the "CurrentUser" from the container). In addition, all other tests we have that are creating this object will fail with the same exception.
This is the equivalent of a runtime error. Whenever we have a choice between a compile-time error and and runtime error, we should pick the compile-time error.
Wrap Up
As we've seen the Service Locator pattern is a valid DI pattern in that it addresses the issues that we are trying to resolve with Dependency Injection. However, it does have a flaw: it keeps the actual dependencies hidden.
When we have a choice between the Service Locator and Constructor Injection, we should favor Constructor Injection. Constructor Injection makes the dependencies completely obvious to whomever is using the class. And if the dependencies need to change, the result is build failures -- we don't have errors accidentally slipping into compiled code.
I have used the Service Locator pattern in production code. And it did work. But I did run into the issue described here. When we added another dependency to an object, the unit tests would build successfully but then all fail (it's a bit disconcerting when you have 30 unit tests fail at once). The test failures were not a problem with the code, they were a problem with the test setup. Since the dependency was hidden by the Service Locator, it wasn't until we received these failures that we realized we needed to go in and add the dependencies in the setup.
So, if you're willing to live with the shortcoming, you can consider the Service Locator a DI pattern. For everyone else, consider it an anti-pattern. As with everything, we need to understand the pros and cons of each tool that we use so that we can make informed choices. This is how we build maintainable, extensible code.
Happy Coding!
Seemann refers to the Service Locator as an anti-pattern -- meaning that we probably don't want to use it in most cases because there are better patterns out there. But he also notes that he used the pattern for many years before coming to grips with its shortcomings.
As mentioned in the book review, I have also used the Service Locator, and I was a little surprised when it was referred to as an anti-pattern. But in reading Seemann's description of the shortcomings, I had no choice but to agree. I had actually come across these shortcomings in my own code, but in that environment, it made sense to continue to use the pattern.
So let's compare the Service Locator to one of the other DI patterns (specifically, the Constructor Injection pattern). This will point out the problems that I ran across -- which also happen to be the problems that Seemann mentions in his book.
Constructor Injection
We'll start by taking a look at the Constructor Injection pattern. For a detailed description of Constructor Injection, refer to my presentation "Dependency Injection: A Practical Introduction".
The basics of Constructor Injection is that we pass the dependencies that a class needs as parameters in the constructor. Here's an example:
In this constructor, we are injecting an IPersonService and a CatalogOrder. The constructor then assigns these to local fields that can be used by the class. Whatever creates this object is responsible for supplying these dependencies. Generally, this is done in the composition root and often involves a Dependency Injection container.
Now, let's compare this to the Service Locator.
Service Locator
The Service Locator varies a bit from Constructor Injection. As mentioned above, with Constructor Injection, something else is responsible for resolving the dependencies that are needed by the class.
In contrast, when using the Service Locator, the class itself is responsible for resolving its dependencies by asking for them from the Service Locator. This can be a DI container or some other object or method that can return dependencies. In our sample, we'll use a DI container (Unity).
Here's a sample constructor:
Notice that the constructor is passed the entire DI container (although there are other ways to get the service locator into the object). Then the object uses the service locator to pick out its own dependencies. This takes the control away from the composition root and gives it to the object itself.
Note that just because you are using a DI container doesn't mean that you are using the Service Locator pattern. The Service Locator is defined by how the container is used (i.e., whether the object is resolving its own dependencies).
On the surface the Service Locator appears to hit all of the points of Dependency Injection: it allows for extensibility; it allows for the swapping out of test doubles; it allows for late binding. This is why it is considered by many to be a valid DI pattern.
But it has a major shortcoming: It hides the dependencies that are being used.
This might not seem like a big problem, but let's take a look at some unit tests to show the shortcoming in action.
Initial Unit Tests
We'll start by unit testing the constructor that uses Constructor Injection. Here's our test:
Notice here that when we instantiate our CatalogViewModel, we must provide the both the IPersonService (_personService) and the CatalogOrder (_currentOrder). If we didn't provide both of these parameters, the test code would not build. As a side note, both of these dependencies (_personService and _currentOrder) are mock objects that are created in the test setup (not shown).
This test builds and passes with our current code.
Now let's look at the same test for the constructor that uses the Service Locator:
In this test, we must pass in a populated Unity container (called "_locator" in this case). As noted in the comments, our "_locator" has both an IPersonService and a CurrentOrder registered in its catalog (these are added in the test setup).
This test builds and passes with our current code.
The Problem with Hiding Dependencies
With Constructor Injection, we can easily see that we have 2 dependencies just by looking at the constructor. However, with the Service Locator, we don't have visibility to the dependencies from the outside (meaning, looking at the public API signatures of the class).
Let's see what happens when we add a 3rd dependency. Our Constructor Injection implementation now looks like this:
The first thing that happens is that we get a build failure of our unit test. Note the squigglies below:
Build failures are good. They give you immediate feedback that something is wrong. In this case, we can see that we have a missing dependency since the constructor wants a 3rd parameter. We would see this same error throughout our code base wherever we try to construct a CatalogViewModel. And we know exactly what to fix.
But what about the Service Locator. Let's add another dependency:
If we rebuild our application, everything builds fine -- including the existing unit test for the constructor that uses the Service Locator. The problem is when we run the tests.
Since our tests do not know about the 3rd dependency, the test setup does not load it into the Unity container. So, this test will throw an exception (when it tries to resolve the "CurrentUser" from the container). In addition, all other tests we have that are creating this object will fail with the same exception.
This is the equivalent of a runtime error. Whenever we have a choice between a compile-time error and and runtime error, we should pick the compile-time error.
Wrap Up
As we've seen the Service Locator pattern is a valid DI pattern in that it addresses the issues that we are trying to resolve with Dependency Injection. However, it does have a flaw: it keeps the actual dependencies hidden.
When we have a choice between the Service Locator and Constructor Injection, we should favor Constructor Injection. Constructor Injection makes the dependencies completely obvious to whomever is using the class. And if the dependencies need to change, the result is build failures -- we don't have errors accidentally slipping into compiled code.
I have used the Service Locator pattern in production code. And it did work. But I did run into the issue described here. When we added another dependency to an object, the unit tests would build successfully but then all fail (it's a bit disconcerting when you have 30 unit tests fail at once). The test failures were not a problem with the code, they were a problem with the test setup. Since the dependency was hidden by the Service Locator, it wasn't until we received these failures that we realized we needed to go in and add the dependencies in the setup.
So, if you're willing to live with the shortcoming, you can consider the Service Locator a DI pattern. For everyone else, consider it an anti-pattern. As with everything, we need to understand the pros and cons of each tool that we use so that we can make informed choices. This is how we build maintainable, extensible code.
Happy Coding!
Subscribe to:
Posts (Atom)