3 ms·
The forced DI pattern makes sense here because: Most DI containers are not just injectors, but they also manage life cycles/scopes of objects: singleton/transi
by BatteryMountain 6y ago
The forced DI pattern makes sense here because:
Most DI containers are not just injectors, but they also manage life cycles/scopes of objects: singleton/transient/scoped.
Since in web development, a request hitting a controller endpoint is typically short lived and most objects and services are tied to the main request scope, it makes sense that all your objects should live and die when the request is made and completed. What is the cleanest way to manage this life cycle scopes? Dependency Injection. Most of your object/services will be "scoped", with fewer being transient (aka a pool of reusable objects) and the fewest being singletons that will survive individual requests.
A second side effect of DI, it forces you to code by convention. Conventions make maintaining the code 6 months from now a breeze. Want to add a new service? Just let it inherit from an IService and consume it in 10 places, it will auto find/inject your new code. No need to new() up and dispose your service in 10 place.
Do all of this without DI and use only constructors + Dispose(). Good luck! Mercy on the poor soul who has to maintain all that mess.
If you want to go a step further, instead of having 100's of IService and each constructor declaring they each need 10 of them (ILoggerService, IAccountService, IEmailService, IStockService etc etc)... that also becomes very gross in large projects: so to go a step further, use the Mediator pattern. You can either implement your own (super easy) or use the Mediatr nuget package. Then each service becomes much cleaner. Each service then only has 1 method that does work. It also ties in nicely with the Unit of Work pattern.
Why should my IStockService inject the IEmailService when I just want to check stock levels with GetLatestStock() that return an int and does no other work?
Lets say the IStockService has 15 methods to do with stock and 10 dependencies. So on each request where you depend on IStockService, you would also pull in the 10 dependencies and their inner dependencies which may add 1 second to your response time and uses 10mb RAM. But all you wanted to was check stock which takes 10ms and 1mb RAM.
This is exactly the scenario that mediator pattern can help solve. So your 15 methods become 15 individual classes, each pulling in ONLY the dependencies it needs and nothing more. Your file and git commits stay small too which makes everything more digestible (less than 100 lines of code if lucky),
So my favourite setup is DI + Mediatr. Each file/class has only ONE purpose to exist and only one reason to be pulled into a dependency tree, all while DI will manage their life cycles.
Edit: For those downvoting, please go read "Dependency Injection by Mark Seemann" and also read https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?view=aspnetcore-5.0 https://docs.microsoft.com/en-us/aspnet/core/performance/per... . Then try to build your own framework from scratch. Do it. Then come crawl back and upvote this comment. I've have a ton of experience with this stuff and have built 2 or 3 custom frameworks. The core trait that keeps everything sane and fast is to keep things simple, keep dependencies low, keep hot paths lean. The easiest way to get to that place is DI + Mediatr and to break some services into their own api's if they get deployed too often. It's that simple.
- Netcob 6y agoI totally agree. It took me a while to wrap my head around it, but my code has improved so much once I did. Very small classes that each do one easy to understand thing. All the structure stuff is in one part of the code, ready to be understood and modified, instead of scattered across tens of modules. Most of the time all I need for a new feature is a class that implements a specific interface, announces to the world what it needs to do its job by declaring its dependencies in the constructor (as intended by the language and OOP in general) and then only implements its core logic instead of meddling with creation and disposal that has nothing to do with its actual job.
- garethrowlands 6y ago> Most DI containers are not just injectors, but they also manage life cycles/scopes of objects: singleton/transient/scoped. That imperative lifecycle stuff is a big problem. Is that idiomatic in .net? Ideally, the program creates per-request data and then the garbage collector disposes of it after the end of the request (or earlier if it doesn't need to be retained until the end of the request). If you need to actually call Dispose, short of `using ...`, you have a problem. > Lets say the IStockService has 15 methods to do with stock and 10 dependencies Is this idiomatic in .net? It's obvious that that is a problem and that smaller classes/functions are better. Normally, your programming language and environment would let you create smaller classes/functions and call them as necessary. Why is something more complicated required in this context?
- BatteryMountain 6y agoI'd say the IStockService with 15 methods and 10 dependencies are not a .net thing but rather <enterprizey-domain-driven-design-all-the-things-coolaid> thing. I've seen it in more than one shop and it totally makes sense why it comes into being. DI just hides the problem because such a class is now only 500 lines long instead of 2000 lines, so for some people that is acceptable (not for me though). You don't see any lifecycle code in your classes anymore since the DI container manages it for them. Remember it is typical to see a business project with 300 classes/tables if the project is about 5 years old and never seen a major refactor/rewrite, and each of those classes gets manipulated in some form from an orchestration point (xManager class or xService class), which in turn might depend on each other or have 10 inner dependencies. To keep things "simple" most guys would just add another method to an existing service where it seems to make the most sense. I argue against that cause the services become heavier and heavier over time and can do allll sorts of things after 5 years (aka IStockService can now check stock, send email notification, generate invoice, save pdf to disk etc). So I've come to the conclusion its better to have more folders/files to contain handlers, and each handler only has one Execute/Process method, and it's constructor declaring only the minimum amount of dependencies to do it's work. It also encourages ALL your services to do only one thing: you know and can trust that IPdfDiskPersistor only does one thing and doesn't have other weird side effects (like trying to send an email). If all this is really not clean enough, then maybe an ActorModel framework like Orleans Framework might be better suited, but I haven't been able to convince anyone at work to use it instead of DDD (they are slightly at odds with each other). Hope that answers some of your questions. If other are reading this, please think twice before you go down the DDD path - it is expensive both in code and time and can easily spiral into a monster (but also well worth it if a good developer have stewardship of it in the long term - keyword being stewardship). Unfortunately teams with high turnover is basically doomed to have a messy/failed project.