The Scenario
In the sample code, we take a look at an application that is split into layers (each layer is a separate project):
- View
- ViewModel
- Repository
- Service
The code from the ViewModel constructor looks like this:
There are several drawbacks to this (as is mentioned in the presentation). One is that due to the tight coupling between the ViewModel and the Repository, it would be difficult to add a different repository type (like one that uses a SQL or CSV data store). We would end up modifying our ViewModel with some sort of conditional to decide what sort of repository to use.
But the real question is whether the ViewModel should even care what kind of repository it's using? The answer is No. To fix this, we added a layer of abstraction (a repository interface) and used Constructor Injection to pass the responsibility of deciding what concrete type is used to someone else. We end up with the following code in our ViewModel:
This is good for our ViewModel: it is no longer tightly coupled to a particular repository; it only knows about an abstraction (the interface). We can remove the project reference to the PersonServiceRepository assembly. If we want to use a CSV repository (that implements IPersonRepository), then the ViewModel does not need to change (and this is what the presentation code actually shows). Success!
Moving Responsibility
But someone has to be responsible for deciding which concrete Repository we're using. The sample code moves this into the UI application (we'll have a bit more explanation of why this was chosen for this sample in just a bit).
The App.xaml.cs has the startup code for our application. Here's where we put the objects together:
What we have here is commonly referred to as the "Composition Root". This is the spot in our application where our objects are assembled -- we're taking our construction blocks and snapping them together. This is where we instantiate our Repository and pass it to the ViewModel. Then we take our instantiated ViewModel and pass it to our View (MainWindow.xaml).
If we want to change to a different Repository, we just update our composition root. We don't need to change our ViewModel or any other code in our application.
The Question: Are We Just Moving the Tight Coupling?
This is where the question came up. We've moved where we are instantiating our objects into the core application project. But now the application needs to have references to the PersonServiceRepository as well as the MainWindowViewModel and everything else in our application. Are we just moving the tight coupling?
The answer for this example is technically "Yes". But that's not necessarily a bad thing (and if we don't like it, there are other solutions as well). Let's take a closer look.
I put together the sample code in order to show the basics of Dependency Injection. This means that I simplified code in several places so that we can better understand the basic concepts. In the "real" applications that I've worked on, we did not have a hard-coded composition root, it was more dynamic (we'll look at this concept in a bit).
But even with the tight coupling in our composition root, we have loose coupling between our components (View, ViewModel, Repository). This means we still get the advantages that we were looking for. If we want to add a new Repository type, we don't need to modify our ViewModel. We do need to modify our composition root, but that's okay because our composition root is actually responsible for snapping the blocks together.
In the full sample, we also see that even with the tight coupling in the composition root, we still get the advantage of extensibility (by easily adding different repositories) and testability (by having "seams" in the code that make it easier to isolate code for unit tests).
But, we have some other options.
Bootstrapping
When using dependency injection and/or creating modular applications, there's a concept of "bootstrapping" the application. The bootstrapper is where we put the code that the application needs to find everything that it needs.
We could easily add another layer to this application:
- Bootstrapper
- View
- ViewModel
- Repository
- Service
This gives us a good place to start looking at the dynamic loading features of our tools. The sample code shows how we can use Unity to explicitly register our dependencies (through code or configuration). But we can also write a little bit of code to scan through a set of assemblies and automatically register the types that it finds there. If you want to pursue this route, you can take a look at Mark Seemann's book (Dependency Injection in .NET) or perhaps look at a larger application framework (such as Prism) that helps with modularization and dynamic loading.
Lazy Instantiation
Our sample shows "eager loading" -- that is, we're creating all of our objects up-front (whether we use them immediately or not). This is probably not the best approach for an application of any significant size. Again, the sample is purposefully simple so that we can concentrate on the core DI topics.
But we don't have to create our objects at the beginning. For example, when using Unity, we just need to put our registrations somewhere before we use the objects. The most convenient place is to register the objects that we need in the composition root / bootstrapper.
Registration does not instantiate any of these objects -- it simply puts them in a catalog so that the Unity container knows where to find the concrete types when it needs them. The objects are created when we start to resolve the objects from the container. Our sample container code shows a "Resolve" of the MainWindow right in the composition root. This has the effect of instantiating all of the required objects (the Repository, ViewModel, and View).
But we could resolve the View only when we are about to use it. In that case, none of the objects are instantiated up front. They get loaded as we need them.
This is a great approach, especially if you are doing modular programming. And this is exactly what we did in a recent project that uses Prism. In that project, we had a number of isolated modules (each module contained the Views and ViewModels for a particular function). When a module is created, it registers all of it's types with the Unity container. Then when a particular View is requested (through the Navigation features of Prism), that View is resolved from the container. At that point, the View is instantiated (along with any dependencies).
Lots of Options
There are a lot of options for us to choose from. Ultimately, which direction we go depends on the requirements of our application. If we don't need the flexibility of swapping out components at runtime, then we can use compile-time configuration (which still gives us the advantages of extensibility and testability). If we have a large number of independent functions, then we can look at creating isolated modules that only create objects as they are needed.
Remember that Dependency Injection is really a set of patterns that allows us to write loosely-coupled code. The specific implementation is up to us. There are plenty of examples as well as tools and frameworks to choose from. Take a quick look to see which ones will fit in best with your environment.
Happy Coding!
In this case you have one Window and one View and ViewModel, what if you need to show several views and viewmodels depending on user action. Each View and ViewModel has its own dependency and I can't see how these would fit in OnStartup(). Any tips?
ReplyDeleteHi Piotr. There are a lot of options for composing objects. For example, on a large application I worked on, each module was responsible for putting together the screens/data that it needed during the module initialization. This moves the responsibility down the chain (and the modules were lazy-loaded in this case). Specifically, I used the Prism framework: https://github.com/PrismLibrary
DeleteThere are also a number of options for composition by convention, meaning if we name our objects a certain way, then our application framework and/or DI container can discover them dynamically in our dlls and put things together for us.
And things are quite a bit different in the web world. In ASP.NET MVC, it's common to have a custom controller factory that has the DI bits built in. Ultimately DI is just a set of patterns with lots of different implementations. The good news is that there are a lot of smart people who have been figuring these things out, so we can borrow their ideas.
I just watched Mark Seemann: https://vimeo.com/68378923 and he promotes building the entire object graph in your composition root, in my case its suggested to use the OnStartUp() in App.xaml.cs. There are other issues, such as some of the dependencies are only know runtime, based on user interaction. The aim is to avoid DI containers until we in the team understand DI, lifetime management and all other issues that will arise where some of the solutions are provided by the DI containers.
ReplyDeleteI'm surprised that I haven't found any examples using multiple views and viewmodels, created in a single composition root. There are a lot of MVVM bits and pieces floating around, some of which do use DI and vice versa, but not a lot that are very strict on both patterns.
What you are saing is multiple compositionroots and lazyloading the modules and I was thinking of several composition roots for a while as well, but now I'm just more confused.
Just like with all of our tools, there are pros and cons to the various approaches that we take to composing our objects. Lazy loading buys us faster application start-up times (and often less memory usage) with the downside of slower response when we open modules later in the lifecycle. Resolving everything at startup costs us a longer startup time with higher memory usage, with the advantage of faster response times when we move from module to module. It's sometimes hard to choose since there is no "one right way", but we have the freedom to focus on the goals that are important to our particular application.
DeleteIf it was easy, then they wouldn't pay us enormous amounts of money to do this ;)
(BTW, I really like Seemann's approach to DI. When I teach people about DI (including my Pluralsight course), I do not use a container; it's important to get a good grasp on the concepts. Then we can introduce containers later if we need the functionality. More here: http://www.jeremybytes.com/Demos.aspx#DI )
I would like to echo Piotr's question about multiple views and viewmodels. Having just read Mark Seeman's "Dependency Injection in .NET" and Gary McLean Hall's "Adaptive Code via C#", it is one of the things that still confuses me.
ReplyDeleteMark Seeman's section 7.4 "Composing WPF Applications" only deals with resolving the MainViewModel. If other view models need dependencies injecting, where are their IoC.Resolve calls made? Would that not go against what Mark Seeman says about only calling Resolve in one place, to avoid the ServiceLocator anti-pattern?
Or is this what ViewModelLocators do - and do they effectively have permission to call IoC.Resolve in carefully controlled circumstances?
Hi Monique. It's more important that we understand the spirit and intent of a pattern (or set of patterns like we have in Dependency Injection) than to follow any particular rules. This way we can explore different implementations while still staying true to the pattern. The guidelines that we find are a great place to start, and they often help us avoid breaking the pattern. But we shouldn't get too hung up on them.
DeleteFor example, instead of worrying about how many times we call "IoC.Resolve", we should be concerned about where we're making those calls. If we want to avoid the Service Locator pattern, then we do not want the objects themselves calling "Resolve", but there's nothing wrong with having an object creator/factory that calls "Resolve".
I've built an application that had dynamically loaded modules (meaning, we loaded the modules at runtime and the didn't create any of the views/view models until the user requested them). The result is that each module resolved its own views and view models. So we had multiple places where objects were resolved, but the objects were not resolving their own dependencies.
This is a much bigger topic, and I'll put some more thoughts down on this in a full article. This is great to dive into deeper. You and Piotr are not the only ones with these questions.
Thanks, that makes a lot of sense and is actually really helpful to hear you say this. The problem I have, being a relative novice to these concepts, is not that I'm trying to get hung up on it, but that I start thinking I must have misunderstood something.
DeleteHave couple questions related to your topic. Is it possible to get answer here or maybe via email?
ReplyDeleteI've coded a great javascript Dependency-Injection framework that is really made for helping in clean Composition-Root creation,
ReplyDeleteit's documentation can be a good support to understand the Composition Root design pattern and how this approach deal with Dependency Injection.
Take a look at Di-Ninja https://di-ninja.github.io/di-ninja/