Why I dislike inversion of control containers

แชร์
ฝัง
  • เผยแพร่เมื่อ 25 ส.ค. 2024
  • Become a YT Members to get extra perks!
    www.youtube.co...
    My Products
    🏗️ WDC StarterKit: wdcstarterkit.com
    📖 ProjectPlannerAI: projectplanner...
    🤖 IconGeneratorAI: icongeneratora...
    Useful Links
    💬 Discord: / discord
    🔔 Newsletter: newsletter.web...
    📁 GitHub: github.com/web...
    📺 Twitch: / webdevcody
    🤖 Website: webdevcody.com
    🐦 Twitter: / webdevcody

ความคิดเห็น • 68

  • @tmanley1985
    @tmanley1985 หลายเดือนก่อน +19

    The benefits of dependency injection are not relegated to tests. Of course mocks can be used. Dependency injection can also help you reckon with your dependencies. You're taking your implicit dependencies and making them explicit. So when your function signature starts to get too rowdy you may find that some parameters clump together often and it may be the case that there's a tortured abstraction in there somewhere. This also has the side effect of forcing you to consider the responsibility of the module, class or function you're dealing with. As dependencies grow it may be the case that it's responsible for too much.
    As for inversion of control containers, the language/framework can make it much easier to utilize. Laravel for example just handles this for you and php's reflection allows for this to be relatively unobtrusive. The indirection you're talking about however is a tradeoff. I could make a parallel argument about the use of interfaces with something like typescript. So your function depends on a Writer interface, now there's the immediate question of well what is implementing that and where is this function being called from. Even paradigms like OOP or functional with monadic interfaces, applicatives, etc. can have massive indirection but the benefit is high cohesion and low coupling. I agree with you however that it's not always needed and can be overkill especially for simpler projects.

    • @WebDevCody
      @WebDevCody  หลายเดือนก่อน +2

      I do think IoC containers and DI has some use cases, but also coding everything against an interface produces a ton of indirection with very little benefit other than being able to say "low coupling" (an academia term that honestly means little to me imo). Regardless if your code depends on an explicit or implicit interface, you still have a dependency on the implementation (either at compile time or runtime). If that implementation has a bug, your code breaks. If you need to change either of those interfaces, you're code needs to be refactored every place you reference those interfaces. To me, it feels like the only useful aspect of coding to an interface is the ability to easily swap out functionality and testing (and changing how you code to make it more "testable" feels like a code smell to me imo).

    • @tmanley1985
      @tmanley1985 หลายเดือนก่อน +6

      @@WebDevCody I think you're underselling the idea of low coupling and using it in the pejorative which is unfortunate because it's desirable for many reasons especially in large complex systems. But you've also just explained why it's useful with your last sentence, the ability to swap out implementations because you're less coupled. But this isn't just to make something more testable as you've laid out yourself.
      Now I think an argument to be made may be that you may not need to swap underlying implementations often. For example, there's debate in the laravel community about whether or not to use Eloquent an Active Record implementation vs Doctrine a Data Mapper implementation. The idea being that if you were to ever change frameworks, the way you interact with your persistence layer doesn't need to change. But most people don't believe that's necessary but that's more of a probability argument.
      Also, I'd like to see which code smell you think IOC applies to because I guarantee that the guy that wrote about Code Smells wouldn't agree with your position. That doesn't mean you're wrong btw.
      But also, I love that you actually took the time to respond to me instead of just viewing my response as someone hurling drive-by insults.

    • @WebDevCody
      @WebDevCody  หลายเดือนก่อน +3

      @@tmanley1985 when you say complex systems or low coupling, I’d be open to hearing exactly what this solves. I work on a code base with 500k lines of typescript and we do a pattern similar to a IoC container. It’s great for when we need to swap out functionality based on if we are running code locally, or in aws, or somewhere else. Other than that benefit, I see little issue with just referencing modules directly and refactoring when necessary. Low coupling means very little to me, but maybe building a more stateful system like a game engine stuff like this would start to shine. When developing web apis, I see zero value.
      But yeah, I like making videos like so others can challenge my current way of thinking. I used to be big into clean architecture, dtos, interfaces, etc, but im starting to see striping some complexity away makes coding much easier and enjoyable.

    • @tmanley1985
      @tmanley1985 หลายเดือนก่อน

      @@WebDevCody First, thanks for taking time out of your day to respond once again. I'll give you the last word on this because I'm fairly sure you've got better things to do than go back and forth with me on the internet.
      Well I suppose we should start with what low coupling means. Without getting into different types of coupling I think you could define it operationally as trying to guard against change by depending on contracts, not implementations (dependency inversion) along with trying to minimize dependencies to only what is necessary. So it's important that we not conflate IoC and low coupling. IoC allows for low coupling. You don't necessarily need an IoC container to achieve low coupling, you can do this with a robust type system. Haskell for example doesn't have an IoC container but nevertheless achieves low coupling through its type system.
      So here again, I think you've made a case for low coupling, you're just approaching it differently.
      Now on the subject of referencing modules directly and refactoring when necessary, I think we need to talk about DI rather than IoC for a moment. My point is just as I've stated before. I think the merits of DI are testability (but of course you can achieve this through mocks if you'd like, so perhaps you think this cancels out), being able to swap out implementations *without touching the original code* meaning the code is open closed, and forcing oneself to confront the sheer number of dependencies is another benefit. When you have your dependencies laid bare before you, because they are colocated the sheer number of them becomes apparent and you can choose to group some together (e.g., an Address object rather than separate fields for line_1, zip, etc.) or you can choose to limit them. An example could be if you're pulling in a stripe module and using it but then you need to switch to paypal, of course you can just find wherever you use that and make changes, but with DI you just inject a different dependency. Now that doesn't solve the problem that IoC solves obviously. To achieve true open closed behavior, you need to depend upon contracts usually through your type system or otherwise. TLDR on that, it's not that it isn't possible to achieve this without DI, but I think DI encourages these things.
      On the subject of clean architecture, I also can see where too much abstraction and indirection can lead to confusing code that often always feels like it's somewhere else. I think there's a line there somewhere I honestly don't know where it is. So I can agree with you there.
      I look at IoC, DI, coupling, etc. as a limit that you should approach rather than a final state. So I don't believe they carry "zero value" or are just "academic" concepts that have no relation to real coding. That treads too far in the opposite direction.
      Thanks again for the charitability.

    • @cyberreaper855
      @cyberreaper855 หลายเดือนก่อน

      @@WebDevCody Think about it this way: Your function or code should work no matter where e.g. data comes from. When you need to perform some calculation it does not matter whether the data comes from a file, a list or a database. And that is a reason why one wants to code against an interface. Same with the calculation, You can change the way something is calculated without changing the interface and thus don't need to refactor your application much. You also gave the example of working locally vs in AWS. You change some config but the application continues to run without code change. IoC and DI also become interesting when starting to extract libraries of commonly used code. Think monorepo with multiple projects. Then the loose coupling really helps in separating without too much refactoring.
      BUT for small projects, this can be too much! However if they then grow adding DI will help in managing them.

  • @ahmedbg91
    @ahmedbg91 หลายเดือนก่อน +4

    I worked with inversify and Nestjs and i stopped using both because of decorators and they both felt more vendor lock in than other js libs and frameworks 😬

  • @zabialy2919
    @zabialy2919 หลายเดือนก่อน +4

    After looking at this, I have to say that I underappreciated spring IOC container.

  • @teamvashmmo3218
    @teamvashmmo3218 หลายเดือนก่อน +5

    Called method injection, people are probably only used to constructor injection perhaps. Great vid!

    • @WebDevCody
      @WebDevCody  หลายเดือนก่อน +1

      But what’s it called if you’re doing functional programming

  • @darrenhinde2971
    @darrenhinde2971 หลายเดือนก่อน +2

    As a project grows, this approach will come a nightmare to maintain.
    Great video, I enjoyed your approach and thought on each point.

    • @WebDevCody
      @WebDevCody  หลายเดือนก่อน

      to be covinced about doing it you first need to convince yourself dependency inversion is useful, but I'm not convinced in a majority of cases

  • @andrewcalderwood6102
    @andrewcalderwood6102 หลายเดือนก่อน +12

    Coming from a dotnet background I cant imaging building a dotnet API without an IOC container. It makes different layers of your application very cleanly defined and easy to test.

    • @cas818028
      @cas818028 หลายเดือนก่อน +3

      It does. And no other language or framework over complicates thing quite like the way .NET does

    • @naughtiousmaximus7853
      @naughtiousmaximus7853 หลายเดือนก่อน

      ​@@cas818028Where is the complication? If anything, JS complicates everything. Have you seen Nextjs? Its incomprehensible.

    • @52daydreamers
      @52daydreamers หลายเดือนก่อน +1

      same as Java spring framework

  • @alexpolyakh5831
    @alexpolyakh5831 หลายเดือนก่อน

    It was very informative and helpful!

  • @devsami
    @devsami หลายเดือนก่อน

    When we say, dependency injection, it means that something is going to provide the implementation of the dependent blueprint.
    And that something is the one which decides which implementation to pass, that in most cases are managed by the language/framework, usually called containers.
    This whole is an example of Inversion of Control, where flow is managed by some container.
    Dependency Injection is a pattern to achieve Inversion Control.
    In case of Java Spring, Spring has IoC container which maanges different dependencies creation and destroying feature.
    In NestJs as well we can achieve that, although there is more bolierplate.

  • @lil_nach
    @lil_nach หลายเดือนก่อน

    I do get the concern of close coupling to the DI library, especially compared to other languages like Java or .NET where similar sdks are either baked into the language or an industry standard. However, the same argument could be applied to a lot of other aspects of an application. If I write an app using convex, and they for whatever reason drop the ball, well now I’ve got 30 queries and mutations that need to be refactored to an entirely different strategy. DI is a tradeoff - abstraction > development speed. It’s extremely useful when applied in the right contexts, and cumbersome otherwise

  • @damirsamatofff
    @damirsamatofff หลายเดือนก่อน +3

    Great video, man. I am glad that I am not alone in thinking this way. The InversifyJS thing you used didn't do anything useful except double the lines of code and all that boilerplate nonsense, just to get the thing working. Many devs that I have met think that dependency injection is some kind of OOP magic with classes and don't understand the concept behind it. As you described, it allows passing a dependency (Writer) down to a function via props instead of creating or importing the Writer object internally. It delegates the responsibility of choosing the correct Writer to the caller of the function, basically allowing it to be used with different types of writers without changing the main() function's internals.
    It really amuses me how people blindly follow the patterns and "best practices" without even understanding the reasoning behind them. Just because someone said so, they don't even dare to question the decisions but religiously follow.

    • @Dom-zy1qy
      @Dom-zy1qy หลายเดือนก่อน +1

      I reckon on a really large codebase IoC can be a net positive in terms of utility. Although it does make the code more confusing.

  • @wichaisawangpongkasame9237
    @wichaisawangpongkasame9237 หลายเดือนก่อน

    Nicely done. You explained the concept and implementation details in quite a short time.
    I agree with you that di libraries makes your code tightly coupled with the dependency, there's no arguing on that.
    However, I use TypeDi for TS and the code are quite cleaner than Inversify, the annotation is also optional.
    Also, in VS code you can choose Find All Implementations to show all the concrete methods/functions instead of the interface, but yeah, it's not as seamless as using a simple callback function.
    I still find it's quite useful if you need to inject something to more than one place without the need to create a new instance of it. The reusability and replaceability still worth it for me.
    Anyway, I think dependency inversion could shine more with OOP paradigm when use inject the dependency at the constructor level.
    If one would like to stick with functional programming, I think those di libraries, while still are useful, would be a bit out of place and seem to pollute the simplicity of FP.

  • @lengors7327
    @lengors7327 หลายเดือนก่อน

    1) Depends on the DI container. With Spring in java, for example, you would only need the Writers and the module using the writers, but you wouldnt need a main where you would even configure the dependencies or inject them
    2) This is somewhat true, but in java land, most DI containers nowawadays can figure out most stuff without the dev having to setup anything. When we do need, it's through annotations, some of which are from specifications not bound to that particular library (e.g. jakarta), while others are particular to some libraries, some other DI containers also support it (for example, Micronaut has some support for Spring stuff). And you can always include annotations from both, they tipically have little to no overhead (depends on the retention policy)
    3) this is true for both methods of DI, and not particular to DI container. I.e. if you clicked on the write method from the caller, whether in your first example, or second example, you would only see the interface

  • @efronic
    @efronic หลายเดือนก่อน

    Nice and useful topic Cody, thank you.

  • @timmeehan2365
    @timmeehan2365 18 วันที่ผ่านมา

    Interesting topic, not talked about enough in the JS world imo.
    Just some thoughts: I think in your case, the MyWriterThing should not implement any interface. If we use Clean Architecture vocab, that would be your "use case" inside the core (= your business logic). Interfaces are needed for your ports/adapters that will connect to external services / APIs, such as the file writer which was a good example.

  • @thespiritualjourney369
    @thespiritualjourney369 หลายเดือนก่อน

    nice and informative vid.

  • @AdamtheADHDev
    @AdamtheADHDev หลายเดือนก่อน

    I think (for the most part), that unlike statically typed object oriented languages, dynamically typed primarily functional languages like javascript benefit less from dependency injection frameworks. Like you show in your video, explicit method injection plus jest.mock covers the majority of scenarios where you might reach for a DI framework in another language, until you reach a certain scale where you might want a bit more structure/control. People do love using frameworks/libraries for every little thing though, especially in the javascript ecosystem where it seems to be encouraged.

  • @NaniwaRocky
    @NaniwaRocky หลายเดือนก่อน +2

    Interesting, Lazar Nikolov just posted a video on using inversifyJS 😅

    • @WebDevCody
      @WebDevCody  หลายเดือนก่อน +2

      I need to watch it and expand my view point. I think he is doing clean architecture the "proper way" as described by uncle bob I believe, so it’s useful if you decide to use interfaces for everything in your code base.

    • @nikolovlazar
      @nikolovlazar หลายเดือนก่อน +3

      @@WebDevCody What a coincidence 😅 yes I'm trying to implement CA by the book. I'm also keeping my mind on keeping the whole thing as simple as it can be. I noticed that inversify doesn't force you to use interfaces, so for my use cases I actually bind them to themselves. I do have to use a symbol to identify it, but I came up with a nifty function that returns the implementation based on a string identifier:
      export function getInjection(symbol: K): DI_RETURN_TYPES[K] {
      return ApplicationContainer.get(DI_SYMBOLS[symbol])
      }
      * DI_SYMBOLS is your TYPES object, while the DI_RETURN_TYPES is an interface that keeps "string: type" properties like "IAuthenticationService: IAuthenticationService". I really don't like having to touch two separate interfaces, but I haven't found a better way to achieve it yet.
      So, it is possible to use interfaces only when you need to maintain different implementations (prod, mock, etc...). But you'd have to register everything in the container up to the level you're using the injections (not including that level). That's why you had to also register your "MyWriterThing". I don't like that as well, but it does make sense because you're using constructor injections.
      I did try to develop a "Service Locator" pattern before implementing inversify, but it started to get too complicated to be considered as a normal thing to do in each of your projects. That's why I decided to dip into inversify and figure out how to use it in Next.js. I didn't try the method injection pattern though. That would be be an interesting challenge for me.
      And thanks for making this video! An enjoyable watch, just like your other ones.

    • @WebDevCody
      @WebDevCody  หลายเดือนก่อน +1

      @@nikolovlazar yeah with method injection you only need to pass the minimum for what your action needs to invoke the use case. It helps with keeping your page bundles a bit smaller. Otherwise you usually have a centralized object that ends up importing all your dependencies so it can register them, which means a single page invoking an action would have a larger cold start if running on serverless because it needs to initialize every single dep for your one use case. It’s hard to explain in a comment but we ran into this issue at work where cold starts take a long time because everything has to initialize to wire up the deps. I think a lazy load approach is useful (which a service locator could achieve maybe?). Or singletons?

    • @nikolovlazar
      @nikolovlazar หลายเดือนก่อน

      @@WebDevCody with how Inversify is set up, it does need to load all modules in the container before anything happens, so it should contribute to a cold start, but I haven't tested the impact just yet. When you're doing the method injection, are you lazy importing everything that the use case need just before you invoke it, or you're keeping one action per file?

  • @charliesta.abc123
    @charliesta.abc123 หลายเดือนก่อน +1

    C# does this stuff the best I have to say. But I'm moving on to Go

  • @pinoniq
    @pinoniq หลายเดือนก่อน

    If only there was a built-in way of "binding" "context/this" in javascript

  • @jiasheng613
    @jiasheng613 หลายเดือนก่อน

    which vscode theme is this?

  • @CadisDiEtrama000
    @CadisDiEtrama000 หลายเดือนก่อน

    Maybe this is just my junior brain talking, but what's the benefit of these Containers? I supposed it should be something like oh, let's encapsulate everything, but to me it seems like unnecessary moviing around of code. What do you actually get from it? When is it actually worth using?

  • @ifeoraokechukwu1346
    @ifeoraokechukwu1346 หลายเดือนก่อน

    @WebDevCody, if an IOC container couples your code to inversify, then you just have skill issues.
    Skill up and then you would see you don't have tight coupling as a complaint.
    Cheers

    • @WebDevCody
      @WebDevCody  หลายเดือนก่อน

      Isn’t coupling defined as a module importing another module directly? So if that’s true, in all of your entities we are importing Inject and Injectable from the inversify package. Soon you have 300 classes importing inversify. That’s coupling my guy.

    • @ifeoraokechukwu1346
      @ifeoraokechukwu1346 หลายเดือนก่อน

      ​@@WebDevCody ,
      Hey Cody,
      Sometimes what we call coupling is actually cohesion and what we call cohesion is actually coupling.
      You can use indirection to separate the classes that use injectables from the code logic that makes use of them. You use a wrapper (Proxy) as such an indirection to separate the point of creation from the point of usage. Also, once the TS files are compiled, you are left with plain JS.
      The only thing i agree with. you as a problem of dependency injection and IOC containers is the mess created by the excessive use of interfaces.

  • @n0rthin
    @n0rthin หลายเดือนก่อน

    At 12:08 you've said that Inversify makes code harder to understand because when you go to definition of methods you are being redirected to interface instead of implementation. But isn't it the issue with DI in general and kinda by design? Your first example has the same issue. If your code only depends on interfaces, then this what you should expect to see when you decide to go to the definition. I do think that IOC makes code a little bit harder to read. I have firsthand experience with it on my current project, which heavily relies on Inversify. Each time I want to see implementation of any specific dependency I have to go through interfaces first, which can be annoying, but LSP makes it very easy to jump through code, so I don't know if this is actually a major problem in modern times.

    • @WebDevCody
      @WebDevCody  หลายเดือนก่อน +2

      I agree that both implementations have the same issue where when you’re coding to interfaces you have to do like an extra step to get to the implementation detail.
      My argument, which I don’t think I really pointed out in the video is that I’ve seen a lot of code bases that use IoC containers and they throw everything into it because they hear low coupling is good and coding to interfaces is good, but then have a bunch of interfaces and only one implementation for all these interfaces at that point, I feel like you’re just over engineering your code for the “what if” that will never happen in the future.

    • @devsami
      @devsami หลายเดือนก่อน

      This could also be the case due to the IDE, because when using Intellij I have seen, it shows the usage and the implementation of the interfaces. There could be some plugins with similar features. At the same time, I feel like we don't have a better solution yet in the JS world for IoC, other than going with framework like NestJs

  • @caseyspaulding
    @caseyspaulding หลายเดือนก่อน

    Yea in Typescript it’s not as helpful. But in Java and .NET it is. I think the use case is when you have complex ‘business logic’ that you don’t want in the UI or DB

  • @SeibertSwirl
    @SeibertSwirl หลายเดือนก่อน +2

    Good job babe!

    • @WebDevCody
      @WebDevCody  หลายเดือนก่อน +1

      😘

    • @vinialves12362
      @vinialves12362 หลายเดือนก่อน

      what

    • @SeibertSwirl
      @SeibertSwirl หลายเดือนก่อน

      @@vinialves12362 what.

    • @vinialves12362
      @vinialves12362 หลายเดือนก่อน

      @@SeibertSwirl what what?

    • @SeibertSwirl
      @SeibertSwirl หลายเดือนก่อน

      @@vinialves12362 what. what? what!

  • @ac130kz
    @ac130kz หลายเดือนก่อน

    it's easier to use just a map instead of injectors with decorators, which make a huge mess. Now there's basically no way for you to drop this injection library, when its lazy dev stops supporting it after two years

    • @1879heikkisorsa
      @1879heikkisorsa หลายเดือนก่อน

      This is really bad advice if the application is something serious.

    • @vitorfigueiredomarques2004
      @vitorfigueiredomarques2004 หลายเดือนก่อน

      ​​@@1879heikkisorsawhy? IOC containers libs are just implementation of a pattern, and the pattern is just about using a hashmap to hold dependencies, under the hood, thoses libs does exactly that, just adds a lot of usefull(for some people) features.

    • @WebDevCody
      @WebDevCody  หลายเดือนก่อน

      @@1879heikkisorsadescribe what you mean by “something serious”. I’m all ears to hear you describe what a serious application is you’ve built

  • @kal.leroux
    @kal.leroux หลายเดือนก่อน

    personal opinion I hate decorator I feel like I am coding in a pervert way ( I had to code in using nestjs framework for a few month and I always take age to add something cause I need to check if I solved the problem in a nestjs way ) and IMO can become easily complex

    • @WebDevCody
      @WebDevCody  หลายเดือนก่อน +1

      Agreed

  • @tariksadiku4723
    @tariksadiku4723 หลายเดือนก่อน +4

    oh wow a typescript developer that doesn't like classic engineering solutions, what's next ...

    • @1879heikkisorsa
      @1879heikkisorsa หลายเดือนก่อน

      Haha😂

    • @WebDevCody
      @WebDevCody  หลายเดือนก่อน +6

      I'll be convinced when someone makes a strong argument to what these patterns actually solve. Most of the time I see these patterns just overused because someone once told them it's what good design looks like and now you have a container with 300 interfaces added to it with no one able to explain why it exists in the first place.

  • @patfre
    @patfre หลายเดือนก่อน

    First you praise it in an earlier video and now you hate it? What a hypocrite, I am out

    • @WebDevCody
      @WebDevCody  หลายเดือนก่อน

      It’s ok, you don’t understand the difference between dependency inversion, dependency injection, and IoC containers.

  • @1879heikkisorsa
    @1879heikkisorsa หลายเดือนก่อน +1

    Here it's obvious that the creator lacks experience in bigger production products. Downspeaking loose coupling and not knowing how to jump to implementations directly in the IDE. Sorry but this channel offers mostly noobie level advice.

    • @WebDevCody
      @WebDevCody  หลายเดือนก่อน +2

      describe me a good use case on a bigger production product for me where doing this pattern has measurability improved the quality of your code base. I work on a product with 500k lines of typescript which uses functional injection with implicit interfaces as described in the videos, and it works just fine. Code is just as modular and decoupled. I don't recall saying the word "coupling" once in this video, but go ahead and imagine things.

  • @myYouTubeChannel31
    @myYouTubeChannel31 หลายเดือนก่อน

    tiG buH - best platform for inversion control

    • @vinialves12362
      @vinialves12362 หลายเดือนก่อน

      they control your dev environment, your source code, your deployment, you control nothing