Reviewing Unity
I am sitting at the airport at the moment, having to burn some time before I can get on a flight, and I decided to make use of this time to review the Unity container from the P&P group.
A few things before I start. Unity is a CTP at the moment, it is not done, and that is something that should be taken into account. I have several design opinions that conflict with the decisions that were made for Unity. I am a contributor to Windsor. Overall, I am biased. Please take that into account.
I am also going to put it through some of the paces that I put Windsor through. Just to see how it goes. Again, I am comparing a > 4 years old container to a very new new, another thing to keep in mind. I do not foresee a situation in which I will use Unity over Windsor, but I do hope that this post will be helpful in future improvements to Unity.
A lot of this feedback is based on my experience and may contain comments that can be considered personal preferences.
Let us start with the things that I like:
- This piece of code is very nice, and it is useful in many scenarios where the instance is created by an external source (Page instance, Forms, Web Services, and many more). At the moment, this is something that Windsor doesn't have (I need two find a few hours free on a machine that has a development environment.
myContainer.BuildUp<MyRealObject>(myObject); - I am ambivalent as far as the Method Injection goes, in the abstract, I say cool, nice feature. The practical side says that it is mostly not required. Nevertheless, let us put it here.
- The XML configuration is very clean and easy to follow. At least the sample ones that I have seen. I am not sure if it is possible to put it in an external file (not in app.config), but that is probably my only reservation with that.
- The ability to configure instances from the configuration is cute. Especially if you add the converter support that you have there. It doesn't seem, at least from a brief glance, that you can build complex objects there, though. That is not a bad thing, however :-)
- It was a really good decision, to decouple the container and its configuration.
And now to the things that I don't like:
- The documentation (and a feature which attempts to resolve a type by default, even if it is not registered) direct toward having concrete class dependencies. I strongly dislike this, I think that except for rare cases, an interface should be used. And I generally dislike this feature.
- For that matter, the ability to resolve a type that wasn't registered in the container strikes me as problematic. There were some long discussions on that in the ALT.Net mailing list, and I can see the other side's point of view. Yes, the container should enable this feature, but not in the common scenario. Preferably, add a method that make it explicit what you are doing.
- According to the documentation, having several constructors will result if Unity picking one of them at random, and supplying its dependencies. I find the greediest possible ctor approach much more predictable, and a more natural model.
- [DependencyConstructor] bothers me. This is the container invading into the application in an unseemly manner. It bothers me more that it is not optional if you have more than a single ctor.
- Much worse, from the point of view of container ignorance, is the facilities for property injection. Here you have to specify [Dependency] on all the properties that you want injected. Again, this is the container reaching into my code and messing with that in unseemly ways. My code should have nothing to do with the container whatsoever. By the shape of my object, so will the container work. Not the other way around.
- container.Get<ISomethingMadeUp>() will return a null reference if it can't create the type. I think that this is the wrong design decision. It should definitely throw here. It means that instead of getting a nice exception "ISomethingMadeUp is not registered" I get "Object not set to an instance of an object". While the ability to try resolve from the container is useful, it should be in a separate method. TryGet would be my preference for that.
- Introspection capabilities are missing. That is, there is no way (that I could easily see, and I looked for one, to ask if a given type or a given extension are registered in the container. This is critical for many advance scenarios.
- Something that I consider to be a sever bug, but from the code looks like a design decision, is ignoring missing dependencies when you resolve a component. That is, assume that you component Foo that have constructor dependency on ILogger. If you resolve Foo, and the container will not be able to find an ILogger implementation, it will pass null to the constructor. Read that one again. Instead of getting "Cannot resolve Foo because ILogger cannot be constructed" you are going to get "Object reference not set to an instance of an object".
This is a problem, period. - Yes, you can use [Dependency(NotPresentBehavior)] to change that, but that is again, an invasive approach. Not a solution.
- There is no protection from cycles in the dependency graph A depends of B depends on C depends on A. Again, you need to get a good exception here, explaining the problem. What you get is a stack overflow exception and bye bye to the program.
- No support for generic components. That is, registering IFoo<> and getting IFoo<string>.
- At a certain point, calling a method on the container will raise an event that will be handled by a strategy. Is seems like a very odd thing to me. The case in point is setting a component to be a singleton, I would have grabbed the singleton strategy directly and called that, instead of going through the event.
- Life time support. Right now Unity supports two levels, singleton and transient. That is good, and you can probably extend that with policies. The problem is that those ideas are hard coded into the container interface. Off hand, you need at least local (per thread or per request) models as well. And I think that exposing singleton at the container interface is a mistake.
- I haven't seen how you can control the dependencies yet, which is a very important concern. That is, when I am building EmailSender, I want it to use port 435 and the file logger, instead of the email logger that everything else uses. This is a key feature, but reading the forums, this is currently a problematic feature.
Object Builder 2 Observations:
- I like the use of dynamic methods to create instances, but the implementation (at least at first glance) seems awfully complicated. The main object that I have here is that you have the control flow of generating this method separated over a large number of objects and interfaces. The advantage that this give you is fine grain control over this process, but consider the task involved, this is a highly cohesive process, and you can't really just plug your own stuff to it without properly understanding everything that goes there.
- In the same vein, the use of policies and strategies for everything obscure the intent of the code. The DynamicMethodConstructorStrategy, for example, need to get the generation context, the existing parameter is use for that, which seems like a bad abuse of the given API. I would create an independent component with an API that would explicitly make this dependency. If you wanted the ability to replace that, have it registered in the container by default, then replace that before you resolve anything.
- In the above case specifically, we have the existing parameter that sometimes holds the existing object, and sometimes holds the current context. That is not a friendly API.
I wanted to try Unity's extensibility model, but I think that I put enough time in it already. What I have learned so far makes it pretty much pointless.
I wanted to build the StartableExtension, which automatically starts up components that implements the IStartable interface. On the surface, it is very simple, register to TypeRegister event, get the type and Start() it. The problem is that you need to wait until all the type's dependencies are available. For example, I may register IHealthChecker before I register ILogger, which it need. There is no way in Unity at the moment to enable this scenario.
Something else that I would like to see is Unity's version for Environment Validation. Here is is likely to be possible to do this, but it expose a problem with Unity's dependency model. You can't register arguments for a component, you can only do that globally, and that is if you are willing to perform invasive operation on your components.
Overall, I think that is an okay solution. You can see some Windsor influence in the API and in the configuration, which make me happy.
It has too many red lines there for me to be able to use it (even if Windsor wasn't around), however. Some of the things that I have outlined here (error handling, for example) can be fixed easily enough, I am much more concerned with the design decisions that were made. Specifically, not throwing when a dependency is not found, both in the specific case of directly calling Get() and in the general case of resolving a dependency for a type that is currently being resolved.
The invasiveness of the container is one of the top issues that I would have fixed. That is one of the major reasons that CAB got lambasted.
I remember having to work with Object Builder circa 2005 - 2006, and it was a major pain. Unity looks significantly better than that. As well as the bits of Object Builder 2 that I have examined. I still object to the strategies / policies design, however, it seems like an overly generic solution. And methods like AddNew offend me for some reason. Add( new Foo() ) isn't hard.
And, to save myself a lot of trouble later on, this is not an attack on Unity, this is simply me going over what is there and expressing my professional opinion on an early version of a project.
Comments
I wish the P&P team would work on creating great articles, documentation, visual studio integration, contributing, etc for EXISTING open source solutions instead of just making something that already exists.
I think the ASP.NET MVC project template where you can selected testing framework is the right approach Microsoft should take with other frameworks as well (NHibernate, MonoRail, etc).
Ouch ... that will be a pain to debug ...
Ayende,
Two points. First, even if the XML configuration is tied to the app/web.config, I don't think that is much of an issue, it's always possible to reference an external file from your app/web.config, so what would be wrong with just saying hte configuration lives in your unity.config file from there?
Second, According to David Hayden, Unity does in fact select the greediest constructor by default. If the documentation says it selects one at random (which would be super wierd), then I assume there is a mistake in the documentation.
I also like that you mention you want to add the BuildUp functionality into Windsor, that's a feature I really wish existed right now.
I'm also on the other side of the fence regarding requiring registration of all components, but other than that I agree with everything you said. It doesn't come off as a too biased review. Hopefully, they'll improve things before they get to the real release.
I am glad that it didn't came up as to biased.
Re: Selected Ctor
It selects the ctor with the most params, but that is different than the greediest ctor.
The greediest ctor is the one that all its dependencies can be resolved from the container.
Unity doesn't even have this concept.
As for adding the ability to add dependencies to existing instances. What you would need is fairly simple, add the type to the container, and then execute SetupProperties on the Activator.
I need to find the time to handle that.
When evaluating Unity, I think it is helpful to understand the overall design approach.
First, Unity is comprised of the Unity Container and Object Builder. Object Builder is a component which was first developed in August of 2005 to provide dependency injection/inversion of control capabilities to the Composite UI Application Block. It was subsequently incorporated by Enterprise Library version 2.0 for building up all the application blocks. Both the Composite UI Application Block and Enterprise Library created domain specific containers for their own needs.
Object Builder itself is really an object factory pipeline which can be used for a large range of concerns. At a high level, it works by chaining together build strategies using the Chain of Responsibility pattern. Strategies are chained together to affect the desired overall object creation (e.g. first look for type associations, second look for dependencies, third create the object with its dependencies, forth set properites, fifth call desired methods, etc.) Object Builder can be used to create a number of different kinds of inversion of control containers, as well as other components responsible for the creation of objects. Object Builder could be used to build Windsor, StructureMap, Spring, or other containers.
Unity is the arguably long overdue out-of-the-box container for Object Builder. It provides a facade to Object Builder, and configures a number of strategies for performing dependency injection/ inversion of control. Unity exposes the extensibility of Object Builder through Extensions, and as such you can make Unity do anything that Object Builder can do.
So while Unity itself is new, the heart of Unity (Object Builder) has been around roughly as long as the other containers.
Concerning some of the default choices of Unity, such as automatically resolving unknown types and returning null for unresolved types, this can be configured to work however one wishes.
Unity takes a different approach than many of the other containers in that it doesn't require unnecessary configuration. If it knows how to create an instance of a type then it creates it. In my opinion, this is actually a very reasonable default to be expected from a factory.
Concerning the default behavior of returning null when nothing can be resolved, this is actually where Unity shows itself to be less intrusive than does some of the other containers. While Unity facilitates the ability to control what happens when a dependency cannot be resolved (through Extensions), by default it doesn't impose upon all objects the requirement that all dependencies be required. In many cases, components are designed to take parameters which are acted upon if present, but ignored otherwise. For example, some components may be designed to take advantage of an ILogger if made available, but otherwise perform the tasks it is concerned with without logging. Using a container like Windsor, you don't have this flexibility. Windsor invades your design with its own requirements.
Concerning lifetime support, this is actually an area being reconsidered by the Unity team in response to a few of us who have been giving feedback to the CTP. Check out the unity codeplex discussions to find out more information on this. Choosing constructors is also another area being reconsidered.
Because Object Builder was designed for the CAB and because Unity reflects many of the characteristics of Object Builder, many of the default choices of Unity make the most sense when used within composite/plug-in style applications like CAB. This is true of the project within my company. This may not be true for more traditional, monolithic applications which rely upon a central configuration file and set of known components.
Overall, I'm glad to see Unity arrive on the scene. Choices are always good.
Derek
I guess we won't be seeing a Binsor cousin (Boonity?) anytime soon :)
Um, Derek,
I am well aware of the history of Object Builder.
And while I find OB 2.0 significantly better than the OB 1.0, it is not really interesting. I is still overly complex and not well suited for the task at hand.
Strategies and policies are very abstract concepts, which you have to beat with a stick t get them to do what you want in many cases.
As a simple example, it is as if all you variable definitions were object. Sure, this give you a lot of flexibility, but it is a real PITA.
And no, you can't base something like Windsor on OB. Not without doing a huge amount of work. In fact, more work than if you started from scratch.
I can't speak for the other containers, but I doubt that it would be a good base for them as well.
I just gave two scenarios that are 1) impossible, 2) hard to do with unity.
Yes, it is a good thing that OB has a container (not sure what is valuable in a generic object factory, to tell you the truth), but it is not really exciting by any mean.
I see those defaults as bug. As I said before, defaults matter, a lot.
I haven't seen a good way to make Unity throw an exception whenever it hit one of those obstacles. And I am not interested in any invasive approach.
Saying that Unity doesn't require configuration is wrong. It requires extensive configuration, via invasive attributes, on all your types, to handle anything by the very simple approaches.
Re: null values.
That is a bug. You give the object invalid values.
There are such things as optional dependencies, certainly. But the constructor is generally not where you put them. And, as I mentioned before, this approach means that now you get errors that are far harder to trace and debug than the explicit message from the container.
Please don't make assumptions about the way Windsor works. They are inaccurate.
Both in terms of what it can do and what the design concepts are. Monolithic are certainly not there.
I hope you noticed the number of times that I mentioned that this was a first release, CTP, alpha, etc. Yes, I know that this is not the final version, and I haven't reviewed it as such.
I have merely 3.5 years experience working with containers and dependency injection. That is a tad over the lifetime of Object Builder. My comments are not coming from pure academic background, they come from my experience of throwing just about everything that I could at the container, figuring out how to architect applications with DI, embedding notions of zero friction development and transparent infrastructure.
In that time frame, I had the chance to develop web applications, window applications, messaging application, and probably a few more that I forget.
Those moved from the simple monolithic blocks (I had to learn why this approach was bad) to the extremely dynamic ones (I have application that I need to update on the fly, without stopping the normal operation of the application).
Based on that experience, I can state that those design decision leads to much long troubleshooting cycles, tedious debugging and fragile systems.
Default matters, and I strongly believe that Unity have the wrong ones.
Ok, I'm new to this whole dependency injection thing. I decided to start off with Unity. It does seem to me, based on my small amount of research, to be a simple starting point tool. It may not be as full featured, but it was easy to hook up. So having no experience with any other tool in this realm, I make these comments.
I found the returning of null to make debugging exceptionally hard.
I also ran into a problem with the default behavior to just create an object rather than throw an error. In my case I was experimenting with injecting the container via the constructor. I registered the container's own instance with itself. But then in one of my classes, instead of saying the constructor required IUnityContainer, I mistyped and said just UnityContainer without the I. Well, unity just went ahead and created a new one, but since it wasn't configured when I next tried to create an object inside the instance it through a null reference error.
When trying to debug this, I first started off putting a breakpoint on that creation that failed, and looked at the UnityContainer object with quickwatch to see if I could understand what it was doing. But as Oren notes, there is no way to look at the items registered within the container.
If the purpose of unity is to be simple and easy to use, I actually have to agree with Oren that the defaults are counter to that goal because they make it more difficult to troubleshoot things not being hooked up correctly.
This isn't to say that these features aren't nice, they are. But they're poor defaults. I'd rather have overloads like GetOrNull<>, sort of like how LINQ has Single, and SingleOrDefault. If that makes sense?
One more thing, I'm having a hard time finding guidance on how to use DI. Things like where is the container created, do i create it globally? As a singleton? Pass it around? Recreate when needed? Etc. etc. What's a good approach? That seems like a good paper for the P&P team.
Steve,
I suggest you go to the alt.net list, there is active discussion around those topics there.
If you will send me a list of questions, I can try to answer them (but it probably needs to be a big list).
For starter, it is generally preferred to have a singleton container, init in the beginning of the application.
Ayende,
First, while you may know everything there is to know about Object Builder, my comments were not just for you.
Second, I'm not sure I caught the two scenarios that you couldn't do with Object Builder. If you want to throw exceptions for types that aren't in the container, you can certainly do that very easily by writing an extension.
Third, while I appreciate your opinions about what every container's default behavior should be, I personally have found the default behavior of Object Builder to make the most sense for the types of application's I've developed over the past several years. As I indicated, if you don't like the defaults Unity provides you, these can be changed easily enough.
Lastly, I am certainly not saying that Unity is perfect. There are several design choices that I don't like within the CTP version. Given your vast experience working with IoC containers, I would recommend you offer your feedback to the Codeplex team on their site where your opinions might actually have some impact on the final version.
Steve,
I would also recommend that you join the discussion on Alt.Net. It would be a little easier to carry on a conversation there about the points you've brought up.
For now, I would like to make an observation about the scenario you brought up. The problem you've encountered here was caused by the fact that the among two resolvable types, you instructed the factory to give you back the wrong one. Admittedly, while in this particular case you encountered the problem because of Unity's behavior of resolving types which are actually resolvable without configuration, this could also have happen in other cases where you registered two similar types and simply typed the wrong one.
Another point I think is important to keep in mind is that a container functions from two perspectives: the object using Unity and the object being created by Unity. When using Unity directly, this is a factory usage scenario. If you step back and think about this from a factory perspective, the defaults might actually make more sense. If I ask a FruitFactory to create me an IFruit, I certainly would expect that the factory would need some configuration to figure out what to do. However, if I ask a FruitFractory to create me an Apple then the factory has enough information to fulfill my need without configuration.
Now, Ayende and others might argue here that I should never ask my IFruitFactory to create me an Apple in the first place, but that is really another issue. The default we are talking about here only becomes relevant when you are actually requesting concrete types. If you don't ever find the need to do this, Unity's defaults aren't really an issue here. If you do ever want to do this, I would argue that telling the container that your should build an "Apple" when "Apple" is requested is unnecessary.
If you want to discuss this further, let's do so on Alt.Net.
[Disclaimer: I am the Unity dev lead]
Steve, if you have questions about Unity, might it not be better to post them to the Unity discussion boards[0] instead of this completely disconnected blog or the altdotnet mailing list?
The returning null is a bug, not a deliberate design decision. It should throw, and I'm currently working out how to fix it. There's an unfortunately interaction with things we can't build and the BuildUp method that needs to be worked out. The fix should be in the next drop. I'm also working on getting better, more descriptive exceptions.
We are well aware of the issue with constructor parameters, and are working in it.
Startable is actually easy to implement; I'll put an implementation on my blog this week sometime.
Thanks for taking a look, it's nice to have some informed flames rather than the uninformed ones I've seen on the mailing list.
-Chris
Derek,
Allow me to apologize. I was unnecessarily snippy.
The scenarios I was talking about was the extensions ones, IStartable and IEnvrionmentValidation.
You mentioned in the past that you haven't had the chance yet to evaluate other containers. I strongly suggest that you would do so. Like learning new langauges, new approaches are critical for learning.
You have to understand something about the way I work. If it is causing me friction, it is banished. I don't want to work on a tool to get it to do the baseline right.
As for feedback, what do you think this post has been?
Chris,
Without introspection, how can you implement startable?
Consider the following case:
public class Foo : IStartable
{
public Foo( Bar bar ) { }
}
public class Bar {}
// you can't respond to the TypeRegistered, because you don't have any idea if
// Foo is ready for action or not.
unity.Register<Foo,Foo>();
// now you can start Foo, but again, no information about that
unity.Register<Bar, Bar>();
Also, I wouldn't consider this a flame. This is just a disagreement and pointing out things that I think that are flaws.
It would have been a flame if I had to build a project for production with this at its current state. See my posts about SSIS or MS CRM to see what that looks like.
Ayende,
I agree, learning new approaches is beneficial. Unfortunately, it sometimes takes actually writing a real application with a product and maintaining it for awhile to know how the differences in approach actually work out. I can imagine what it might be like to have to configure every single thing I want to use in my mega-composite application, but I may have to actually do it for a while to see if it could really work. I would say the same would be true for you. While you could image how it might be to use Unity on a large scale project, you may have to actually do it to know. What I can say is that I have used Object Builder, and because it was designed for the very type of application I'm working on it has worked out just fine for me.
Concerning your post representing feedback, it certainly does. However, it is far less likely to actually be of benefit to the Unity development team than if you were to post feedback to their codeplex site.
Derek
Ah, I misunderstood what Startable does. The Windsor documentation doesn't say anything about the semantics, so I assumed it was similar to our existing IBuilderAware interface, which it isn't.
If I understand what it does now (automatically create an instance and call Start when enough dependencies are registered) then no, by itself Unity won't do it. I can think of a couple of ways to accomplish this, but I'll have to prototype it a bit first.
Again, I ask everyone commenting on Unity here or on the altdotnet mailing list to come over to our codeplex site. We only find stuff here by accident, but are actively monitoring the project site.
-Chris
http://www.codeplex.com/unity
Derek,
I wrote a big application using Object Builder as the container.
It was built in the classic MSDN style architecture.
I have learnt a lot of things from that project, but most of them were what not to do.
How do you think I could pull such a large list? I know all the soft spots of all IoC, I was there, I ate the pudding and I was sick in the bathroom afterward.
I didn't get a TShirt for that, though, I wasn't implementing IShirtable.
Another mistake is that I care about Unity in any deep way. This review was sparked by curiousity about Unity, not much more.
I am juggling enough things, I believe. Taking an active part in another project is not realy feasiable.
Chris,
Start a mailing list, that would make participating in Unity far less onerous.
Right now, having to go to a forum site and track that is not a sustainable approach.
I'm not sure your application had the same needs and restrictions as my own, but I appreciate your opinion nonetheless.
As far as you caring about Unity's success, I certainly have made no assumptions in this regard. I only intended to suggest that in light of your experience in this area it would be valuable to share your critiques with the Unity development team. It certainly wouldn't be more work than debating with a nobody within the comments of one of your blogs :)
Derek
Derek,
I enjoy my blog, and the discussions in it.
That is my motivation here.
I also have the blog setup so I basically need to do very little to have this conversatin.
Doing this on the Unity forums is work.
As for my scenario, SaaS app, ~10 modules times 700 clients, each wanting their own tiny change.
Oh yeah, it was composable all right.
I assume you had the ability to manage a central config file to define all the mappings?
Derek
Don't assume, the config was a nightmare. At least half of that was the client's fault.
My project doesn't allow there to be a central configuration file, so any alternate product I would evaluate would have to be capable of being configured through code.
Have you seen how I do this kind of thing?
I am generally working with Binsor, by defining rules for gathering the proper components.
You can do it manually, via code, if you want. But it is generally a major PITA.
What are your thoughts about Autofac IoC Container?
http://code.google.com/p/autofac/
This is not a really scalable approach
Because?
You have to specify all the components by hand.
I am talking here about the impression I got from the first page, by the way.
Having to go and specify how to build each type is not a workable solution in any system.
I don't want to do that, that is the container's job.
You mean it doesn't have configuration engine in Boo ;-) ?
I means that it doesn't figure out, on its own, how to build things.
This means that changing a ctor is a breaking change, for example.
You can specify constructor to use.
builder.Register<MyFoo>().UsingConstructor(typeof(int));
Otherwise you can use Autowiring to cut down on repetitive configuration.
var builder = new ContainerBuilder();
var controllerTypes = from type in Assembly.Load("MyWebApp").GetTypes() where typeof(IController).IsAssignableFrom(type) select type;
foreach (var controllerType in controllerTypes) builder.Register(controllerType) .WithScope(InstanceScope.Factory);
This doesn't preclude you from subsequently overriding one of these registrations to meet special requirements or improve performance:
builder.Register(c => new HomeController(Settings.SiteName)) .WithScope(InstanceScope.Factory);
<Sorry if this formatted badly>
Alex,
For the first version, if I now add a new parameter to the ctor, I have a breaking change.
For the second one, how does it figure out what parameters it should use?
BTW: Autofac automatically chooses the constructor with the most parameters that are able to be obtained from the container. (I think Windsor does the same).
Alex,
Okay, so I think that the docs are point to the wrong direction, but that remove a lot of the objections to it.
Comment preview