DIP is achieved precisely when the components (be they packages, projects, classes, types) are organized with the low-level modules depending on the high-level modules rather than vice versa. In English, this usually means the I/O and any heavy framework code is kept out of the program logic.
What DI does do is confuse this matter. Class relationships which are mere IMPLEMENTATION DETAILS of a module are elevated alongside the actual component architecture of the program. The object graph becomes obscured. Program execution order becomes nondeterministic. The callstack is completely ruined, undoing decades of enlightenment since Dijkstra's "GOTO statement considered harmful." And all my experience with the pattern proves, the DI container causes programmers to not even evaluate or understand whether their dependencies are even inverted, because all classes and their relationships just become one amorphous blob floating aimlessly inside the container.
Simply, this means that no implementation module, high or low, depends on any other implementation module, ever. It only ever depends on abstractions which are shared between modules.
I don't see how using a DI container can change the program execution order, unless one is misusing it terribly - after all, its sole purpose is to provide the correct concrete implementation of a dependency to an object at the time of its construction, the execution order from the perspective of the program is 100% preserved. Sure, maybe the container itself creates my graph in a non-deterministic way, but why would I care? If my program depends on this that's just bad design imo, no amount of libraries is going to save it :)
And the object graph is not obscured, in fact it is clarified, because you look at a class constructor and immediately can see what it's invariants are! And I have yet to come across a DI library that wouldn't immediately halt and catch fire if you introduced a circular dependency chain, so it's literally not possible to have these amorphous blobs (great expression though!) in any proper DI container.
+1. This simple idea is the basis of Clean Code, Hexagonal Architecture, Onion Architecture, Haskell, Functional-Core-Imperative-Shell, among other good architecture ideas.
If you're not using abstractions, you are not using the DIP. What is being described here with low-level modules depending on high-level modules is categorically not the DIP, and I am starting to wonder if this is perhaps part of the frustration that the poster we're replying to has experienced with DI, since turning your coupling upside down will have the same problems as just high coupling in general, except everything is upside down now and harder to read :)
ᴉɟ (ɐ == q) {
ɔ;
}
Or the whole thing? {
;ɔ
} (q == ɐ) ɟᴉ
I want to make sure I do this the right way.I agree with your take on DIP.
We're using Microsoft Service Collection and with the Scrutor nuget component we were able to easily decorate an implementation from another package to extend the functionality adhering to the open closed principle OCP.
DI also enables your code to be open for extension but closed for modification.
Great stuff!
I like C#, no, I adore C#. I have a bad memory for even the simplest method calls and the sheer power the intellisense in a statically typed language gives me to just not care means I can just code with joy.
My personal view on it has always been that someone in MS wrote MVC 1 in response to Rails on the sly. It was fantastic, a breath of fresh air into the MS constant misunderstanding of the web. Especially compared to webforms. For a while, everything was good. Then somehow 7 or 8 years ago the ASP.Net team got obsessed with ramming "best practices" down our throat and everything's gone a bit downhill from there.
And they don't even have a point in this regard, because nothing is forcing you to use the DI. You wanna make a database connection with ADO.NET and a static connection string in your Controller? Go ahead. You can do that. It's no more effort than it ever was.
You want to grow your app to run it in multiple environments and be confident you don't clobber your configuration during migrations? Use ASP.NET Core's DI.
Every app framework has some "magic convention over configuration". I personally think ASP.NET Core's "magic" is a lot less pervasive than in, say Django, or RoR. When I was first learning ASP.NET Core--coming from WebForms--there was a learning curve. You don't just throw everything into a Web.config XML bag of doom anymore, with a single, static configuration reading tool that reads just that one file. That particular magic has changed. I mean, if you want to do that, you can, just read the file yourself. But it's not wired up by default anymore. And the new way of doing things was easily learned with a couple of afternoons of reading the documentation.
Which you can do, because there is actually documentation, and a lot of it. I think part of the problem might be that people are used to other web app frameworks where the documentation is pretty lacking, so they don't even think to go looking for documentation on ASP.NET Core and think they can just jump in and figure it out. I don't think you could do that going from Django to RoR or vice versa, but people complain when they can't go from ASP.NET WebForms to ASP.NET Core without learning new ways of doing things.
Don't like ASP.NET Core? You don't even have to use it. You can (rather easily) write your own web app framework. I've done it in the past, and there are FOSS projects for doing it (https://suave.io/, https://www.abp.io/, https://giraffe.wiki/, https://dotnetify.net/, https://www.dotvvm.com/, https://coalesce.intellitect.com/).
1) that graph of object creation is NOT equal to graph of object usage
2) if you don't use DI, problems won't automagically go away - code will _still_ have dependencies, just they'll be hidden and tightly coupled
It is precisely when you use a DI container that the problem is hidden, whether or not it's solved.
In most projects that use a DI container, the problem is usually not solved correctly, but the developers are oblivious and overconfident. They think, as you do, using a DI container means they did it right. False! The DI container only guarantees everything is hoisted up to the object constructor. This is NOT DEPENDENCY INVERSION. The objects can be in the constructor, but the dependency arrow can still point the wrong way. An obvious example of this I've seen a million times is an interface sitting right next to its only implementation. That's NOT Dependency Inversion.
If you don't use a DI container, the problem also may or may not be solved, but it is never obscured, it is completely visible.
Also, one can use DI without DI containers. Actually I prefer not using DI containers when possible.
Write a util function then, or like some like to call it, one of the many Factory pattern.
https://web.archive.org/web/20200506032508/http://misko.heve...
https://web.archive.org/web/20200502175326/http://misko.heve...
#1 Because the language provides no means to arbitrarily mock fields of a class, an entire design pattern and framework needs to be created just to do so. Why can't the language just have such a feature?
#2 This actually temps people in building worse design, because instead of creating pure units that don't have dependencies to begin with, it facilitates the opposite of creating deeply nested chains of dependencies, because "DI framework wires it up for me". Where if you didn't have that luxury, you'd be pushed towards pure units that don't have state dependencies, and integ tests instead, which in my mind is much better overall.
#2 What stops you from creating a complex system without having dependencies, thus avoiding DI? Also, I think it would make an interesting case study if you're willing to write it.
I don't think I did. Someone says, well if you use new, how are you going to unit test your class?
And one answer from a user of the language would be: Ya, I guess you'd need to change the way you're creating dependencies so they happen in the caller, and then you'd need to change your class so it takes the instance on its constructor, etc.
And this is the original OPs issue with C#, all the ceremony involved.
Another answer would be that the C# language designer could build a language level feature that avoids having to do that and allows mocking new inside a unit so it can be easily tested without shenanigans.
In fact, in Java-land, there is a library that lets you unit tests classes that use new, it's called Powermock. So it is feasible.
> #2 What stops you from creating a complex system without having dependencies, thus avoiding DI? Also, I think it would make an interesting case study if you're willing to write it.
I've moved to a functional language instead personally. That said, I do still like C# and Java, and think they are great languages. I'm also not against DI, or other enterprise patterns, some have legitimate uses. But I have definitely seen what the OP is complaining about, those languages are too deep in their own rabbit hole sometimes. To the point that half the developers don't know why they use DI, that's just part of the template they used to bootstrap their app. There's no thought process about, wait, what is the issue in my code structure and design that I'm facing, and what could I do to solve it. Instead it's just, I'm using all the "best practices", they are the best, and I'm using them all, all the time, so my code is the best it can be. Without any thought about what advantages they're even getting out of it.
That's why I asked about the "new". Most people don't really know the pros/cons. They only repeat what they read blindly: "New" bad bad, no use "new", never, very bad, antipattern, here link proof, must use DI instead.
And sometimes when they know of a cons, they don't think about tradeoffs against alternatives. They just say, well it has this one con, so it's very bad! An antipattern!
I think that creates a setting where you get monstrous framework and code bases, where people just throw every known pattern at it, no matter if it was called for or not.
> just they'll be hidden and tightly coupled
They're more hidden with DI than without, in my experience.
At any rate, having left the .NET stack around 5 years ago, I certainly don't miss DI. My current code has more and better tests than my C# code ever did, so DI didn't really help me there (nor did it hinder me-- it was neutral). But my current code is much more explicit, direct, and a fair bit more compact. I'm definitely happier with it.
To me, answer is clear - class A has obvious and loosely coupled ones. Lo and behold, class A comes from program which uses DI. Class B doesn't.
You can still depend on abstractions without DI. Just use an interface or asbtract class in your code.
DI only plays into how you acquire yourself an instance of the concrete class for that abstraction you depend on.
So it can be given to you by your caller (DI). You can go fetch it somewhere (ServiceLocator). You can create it through a utility (Factory). Or you can create it yourself old fashion way with new.
> But you don't have to use a library; many times I'm writing a simple-ish console app and just use good old "Poor Man's DI" where I manually construct my object graphs at startup
I will support that. I'd like people to be precise in their criticism, do you find the DI pattern troublesome, or some particular DI framework?
I was kind of making the same point as you did, that you don't need to use any kind of DI library to achieve the DIP, but that in something like ASP.Net it is there and simple to use.
I am not sure what is a better way to solve this problem, but the original OP is right - it's far too much ceremony.
I think it is ok to depend on a DI-container in your unit tests for dependencies you are not directly testing, instead of stubs or manually instantiating them, to avoid updating unrelated unit tests when some class changes it's dependency somewhere in your project.
Instead I prefer system/integration tests where you test lets say a complete request from start to finish. Now your code base can change as much as you like, dependencies can be added or removed or classes or packages can be completely deleted, the tests stays the same. And now you are having tests that much closer matches reality instead of tons of mocks and stubs that can easily fool you to believing that they represent a good estimate of reality.
By doing this way a constructor based DI does not become a nuisance instead it helps you to organize your project, as it should be. Using a DI container makes it natural to break down your classes and automatically share the same decencies between them.
Counterargument to my argument is that if you don't use constructor based DI you can rewrite your class in anyway you like and the test stays the same, that is all true, however what DI gives you is the possibility to change the behavior of a class from the outside by injecting different dependencies, this makes your code better structured and easier to reuse.
If you want to accomplish that in the non-DI case you have to go thru trouble of configurable proxy classes which just introduces one more layer of bureaucracy, the thing you wanted to avoid by not doing DI.
My own criticism of DI containers is that they introduce magic to the project, personally I truly dislike code magic, however a DI container is nothing more than on the fly factory creator. In theory you could remove the DI container from a project by manually writing factories for each execution path, for me that becomes an acceptable level of magic as long as the DI container doesn't do lots of other things that factory couldn't solve.
Edit: misspelled convenience
public class MyTest()
{
private readonly IServiceProvider _serviceProvider;
public MyTest(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void MethodA()
{
var serviceA = _serviceProvider.GetService<IServiceA>();
}
public void MethodB()
{
var serviceB = _serviceProvider.GetService<IServiceB>();
}
}
In C# the idiomatic way to achieve this is via constructor injection, which is made much easier to manage in ASP.Net with a DI library that works with the framework for you (either the built in one or AutoFac / SimpleInjector, etc.). But you don't have to use a library; many times I'm writing a simple-ish console app and just use good old "Poor Man's DI" where I manually construct my object graphs at startup.