What's so dangerous about a DI attribute? by Mark Seemann
In a reaction to Uncle Bob's recent post on Dependency Injection Inversion, Colin Decker writes that he doesn't consider the use of the single Guice @Inject annotation particularly problematic. As I read it, the central argument is that
annotations are not code. By themselves, they do nothing.
I'll have to take that at face value, but if we translate this reasoning to .NET it certainly holds true. Attributes don't do anything by themselves.
I'm not aware of any DI Container for .NET that requires us to sprinkle attributes all over our code to work (I don't consider MEF a DI Container), but for the sake of argument, let's assume that such a container exists (let's call it Needle). Would it be so bad if we had to liberally apply the Needle [Inject] attribute in large parts of our code bases?
Colin suggests no. As usual, my position is that it depends, but in most cases I would consider it bad.
If Needle is implemented like most libraries, InjectAttribute is just one of many types that make up the entire API. Other types would include NeedleContainer and its associated classes.
Java annotations may work differently, but in .NET we need to reference a library to apply one of its attributes. To apply the [Inject] attribute, we would have to reference Needle, and herein lies the problem.
Once Needle is referenced, it becomes much easier for a junior developer to accidentally start directly using other parts of the Needle API. Particularly he or she may start using Needle as a Service Locator. When that happens, Needle is no longer a passive participant of the code, but a very active one, and it becomes much harder to separate the code from the Container.
To paraphrase Uncle Bob: I don't want to write a Needle application.
We can't even protect ourselves from accidental usage by writing a convention-based unit test that fails if Needle is referenced by our code, because it must be referenced for the [Inject] attribute to be applied.
The point is that the attribute drags in a reference to the entire container, which in my opinion is a bad thing.
So when would it be less problematic?
If Needle was implemented in such a way that InjectAttribute was defined in an assembly that only contains that one type, and the rest of Needle was implemented in a different assembly, the attribute wouldn't drag the rest of the container along.
Whether this whole analysis makes sense at all in Java, and whether Guice is implemented like that, I can't say, but in most cases I would consider even a single attribute to be unacceptable pollution of my code base.
babakks, thank you for writing. While I'm aware of TypeScript and the overall design philosphy behind it, I've never written any TypeScript code, so I'm not really the right person to ask. As a general observation, though, I recommend Pure DI. Unless you have some rare and exotic requirements, a DI Container is almost never the best choice. DI Containers tend to create more problems than they solve.
In TypeScript, can't you compose object graphs by simply writing the necessary code? That's what I do in C#, at least...