This is why dependency injection is useful
Vložit
- čas přidán 29. 08. 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
This feels like a workaround - I mean, you've separated the deletePasswordResetToken, and updatePassword functions, but then you're wiring them together with the tx argument, so they're kind of decoupled, but not really, because they still need to know that they can be ran inside a transaction, so they feel like leaky abstractions. I feel like you could just create a separate changePassword function with its own database calls inside a transaction and it would be fine to have a little bit of code duplication there.
Yeah that’s a good point
Also DI enables unit testing to prevent hardwired classes being instantiated in the constructor so that mock classes can be passed in as needed for testing purposes
Not necessarily related, but after years of studying and expirimentation I can suggest that mocking should be the exception in ones automatic testing approach. I understand that some classes are really impractical in automatic tests suite, but otherwise one should test mostly at the API level and use as much "real" classes as possible.
@@krajekdev In unit tests, you want to mock everything but the actual unit that's being tested. That may be a single method or a single class.
In integration testing, you want to test the interaction of a unit with another. A class with something else. So you won't use a mock in that case.
Why? Because the goal of the unit test is to prove that just the logic within the thing you're testing works as expected, *given everything else is working as expected*.
Integration tests will provethat the interaction between two units, *which are assumed to be tested already*, behaves as expected
e2e tests will cover the whole system's interactions.
You want tons of unit tests, reasonable amount of integration, and sparingly use e2e tests
@@liftingisfun2350 again, i think that this mantra of tests pyramid is ineffective and i practice approach of reversed testing pyramid. My mocking is very limited.
I know that youtube comment is not a place for the lecture. I just provide it, because years ago i needed the trigger to free myself from false "test pyramid" paradigm.
I was in a very similar situation that I didn't know could be resolved with dependency injection, nice!
One interesting thing is that with React context we can easily have an Angular-style DI container so we can control the lifecycle of the injected object instead of just having everything global or passing all dependencies from function to function.
Create a context whose default value is a WeakMap.
Now in your application, you can create many providers for this Context that add dependencies to the WeakMap.
Create a hook so the components can access the WeakMap and pass the WeakMap to your functions.
Now, for instance, if we want a dependency that needs to load after something shows on the UI and part of the UI needs to show a loading spinner while it is loading, create a Provider that adds that dependency only after it loads (you can use the "use" hook to force some loading screen on the provider).
Interesting, you need to make a video about this
TKDood (maintainer of React Query) writes about this a lot on his blog. He did an excellent article on how to use React Query as your async state manager and React Context as your DI container to use that data elsewhere in your app. It's a pattern I use quite a bit!
U should write an example about this.
I hate being this early, cause after watching a video like that I like to read what others have to say about it, but there's no comments yet and I will probably forget to come back here. 😭
don't worry I'll send you a message in 8 hours and expect you to come back
@@WebDevCody WebDevChady
Here's my own opinion on this and sort of a reminder for you.
This is how I do it, even in other languages I've worked on.
There's always room to pass in a transaction context and with provision of a fallback for synchronous calls.
reference it instead of instantiating it, that's pretty much the gist of DI(dependency injection)
built a chrome extension that reminds you to come back to registered pages
You can also solve this with the Unit of Work pattern. You add all the operations to the unit of work and when you call the Commit() method, it will create a transaction and process the database queries. What is your opinion on this solution?
Among other things, one thing that dependency injection does is to force you to confront and make explicit your dependencies. When your function signature begins to grow, you may start to notice that it's either too big or data starts to clump together often which usually means that there's either another abstraction you're missing, or perhaps the function has too many responsibilities.
Another banger, I'm learning a ton from videos like this! And as I apply these techniques to my own codebase, I've notices my code not only becomes clearer and more maintainable, but I'm also simply writing less code. Keep 'em comin!
Is this really "dependency injection"?
At the heart of dependency injection is "providers can be switched" by simply injecting their dependencies without the called code knowing nothing about providers being switched. In this case, the only switch you can do is either pass the database connection yourself or get it passed by a parent caller. This is more like "pass a default if an object is not passed".
for it to truly be dependency injection, the trx parameter should have worked lets say "to change password in a redis DB and a Mysql Db via the same methods. If the API for change password and delete tokens is agnostic and providers( redis, MYSQL) can be injected, in my book, that is dependency injection.
This is awesome! Love seeing clever and clean solutions like this. Thanks for posting this!
You could also put the database calling functions inside a service object factory with db as an argument, so you'd be able to use dependency injection in every db calling function without modifying its signature, but you'd have to instantiate new objects for transactions
Hadn't gotten much into drizzle and so i didn't even know that they had transactions inbuilt(i guess i need to read the docs beyond making queries). Thank you for the insights. Much love from Uganda "mentor"
Prisma alternative also has it
This is awesome, I never thought about that use case with drizzle. your content is superb, keep em coming!!
Thanks for also answering my question 😁👍
I think best and clean way is using async local storage to shared async context between those methods
Good catch, I've faced issue regarding this, I was deleting some sessions without checking if the other query was done successfully or not. I had deleted 60ish items from my db in this case while testing, luckily it was stage db so everything was fine😅
I really like that JS devs nowadays wish JS to be more like Java than the chaos it was years ago.
So I am working on a transaction feature where it will create a new user account for auth, then create the user own document, in this case an apprentice which will have its own props, then send a registration email.
I didn’t know how to make this totally clean using SOLID and all this guru stuff then I ended up creating my Transaction class with createTransaction method, which then I Dep Injection the MongoDB session, but then some others dbs do in different way, while Mongo use session, others use different way to manage transactions.
It worked anyway 😅
This is some really clean code. Is this React or Next? Subscribed!
We can have transactions for both queries as they are both running on a db, but what do i do if first query is an upload to S3, and then second is adding the metadata returned from S3 to the db. If the S3 one fails, that's fine. but if the second db one fails, do we have to make another endpoint for uploading the metadata back to db and asking the client to perform this action again. Since, S3 upload is already successful and we def do not want to rollback that.
Or is there a better way possible?? Btw love your videos!!
first thing comes up on my mind is retry and put it to a dead-letter queue(if that's not overkill) for the second part of the transaction, but ideally, we want to know the exceptions/errors that cause the failure🤔
I'd personally do it all in a single api endpoint. presigned urls sound cool, but honestly the complexity increases a lot instead of just allowing your api to accept your image and have your api upload to the bucket and also create the db object. you'll be charged more for bandwidth but the simplicity is worth that operational cost imo.
@@WebDevCodyvery much agree here. Since the operations are related, it makes sense to accept the bandwidth overhead in exchange for the simplicity and operational cost that can come out of solving this problem.
Thnx!
Doing it in a single API endpoint doesn't answer the actual question: it's still possible that the upload to S3 succeeds but creating the database record fails.
this is one of the easiest explanation of dependency injection, thanks Cody, but i still do hate the decorator pattern
What theme are you using?
??
It's bearded Theme Stained blue@@alecdorasandler6690
Wow this is super useful
At 2:35 when trx is not sent - as it is optional - what happens at line 75 ? await trx will be null or will db be configured as default if not passed?
a little unrelated but, which keyboard is web dev cody using? sounds so good. might be brown switch but the sound of key strokes are so smooth
Klack app
Dependency injection in functional programming 😂. That's just a fancy way to say “calling a function with parameters”.
Good idea though 👍🏻.
What is drizzle drizzle?
What theme are you using for VSCode?
Would like to know as well
This seems to be One Dark Pro ++ (at least to me). I'm not sure though.
This is just a fancy word for a simple solution
Can you please tell me what is the add-on that shows your Git history on Code?
I prefer procedural programming more where the control flow and commands are all seen together. The results in a lot more lines of code but you get less indirection and fewer symbols overall
In this case, it would be nice to use decorator like @Transactional and have the domain code not even be aware transaction exist
decorators? gross
@@WebDevCody I think you will like it jk. Well I don’t mind it.
over complicated
no, it wouldn't
Maybe I'm being nitpicky, but it seems to me what you have done was inversion of control, not exactly a DI. But awesome content anyway dude, keep it up.
So in your opinion, what would make this dependency injection if injecting the dependencies as arguments instead of importing them is not dependency injection in your opinion. I think there’s a misconception that you have to use some type of injectable auto wiring dependency injection framework for you to achieve dependency in injection and I don’t think that’s true.
@@WebDevCody Yeah, you're right, I may have understood it poorly.
Cool, I have been using dependency injection without knowing what it is or why it exists xD
I would not recommend to call trx as second argument or third one.
what if I need to pass another argument into this function? it would be like
token: string, trx: db, emailCode: string
this is so ugly
so instead have a structure to pass params for such use cases functions
{ deps: { trx }, input: { emailCode, token } }
in this way you can easily extend without having argument hells like
removeToken(token, false, null, 2, undefined)
Functions that can be part of an atom. Quark type? Lol. Would it be nice to have such type & have them applied to functions which would make them easier to find & notice?
This is a useful concept, but in this case with drizzle it seems not to make any difference: "The tx object passed to the transaction function is just a convenience. You can use db interchangeably, it won't make a difference. It only has a rollback() method which only throws an error. Inside the transaction, it stops execution and rollsback"
I realise this is not documented well. I did link to the github issue where this quote comes from, which I guess is why youtube deleted my comment. It's issue 613.
good thing to point out!
I would love a deep dive into dependency injection. I'm pretty new to the concept and I've tried refactoring code on a project using the tsyringe containers which didn't work out too well. Are there any resources or guides you can recommend how to (incrementally) adopt DI?
I liked how i dont have to pass more and more arguments to my functions since the classes were automatically injected into the constructor but that also made it quite hard to split the code / move it outside of the respective classes. Is there a middle ground or a better way of doing this?
Would love to hear your thoughts and how you think about implementing / adding DI to your codebases
idk I always lean towards injecting with function arguments. Using IoC containers or DI frameworks feels like over engineering and tends to result in code that is hard to understand
@@WebDevCody Hey, thanks for the response! I was wondering how you would handle deeply nested function calls. If you have authentication and authorization functions that get called deep inside a big complex function, how would you pass all the different arguments around in that case?
JavaScript is a super dynamic language so there's really no need for heavy IOC containers. Just export module-level constants that hold your authentication and authorization and import them into your other modules. Jest allows you to mock all of a modules' exports so there's no need to introduce an IOC framework to make your code unit testable even after you torn out the function arguments.
Why do you await in deletePasswordToken function if there's nothing below await statement?
updatePassword is an async function, it returns a promise, you have to use await to resolve this promise.
Or maybe use partial application for those two functions and don't clutter their signatures
export const deletePasswordResetTokenWithTx = (tx: typeof db) => (token: string) => ...
export const deletePasswordResetToken = deletePasswordResetTokenWithTx(db)
Thats not dependecy injection... you are simply passing an argument to a function and still is..
Simply that.
Doing that manually doesnt create a dependency injection.
Then tell me what's the difference between dependency injection and pass and argument around ? 😂
Dependency injection is more complex and you can do it with a language like Typescript via Interfaces (but using a simple class would do too).
You are confusing what is it and what is for...
Yes it may look like is passing an argument into "something" (can be a functiom/method or a class constructor) but the concept and end goal is very different
it's called functional injection. There are different ways to achieve dep injection. people using OOP like to do constructor injection, some people using IoC containers, some use setter functions, some use a DI framework. the point of DI is that your concrete implementation doesn't know where the dependency is coming from (it's passed in somehow).
the only part I'd argue that yes maybe this isn't pure DI is because I'm using a db instance as a fallback inside the persistence function, but you're wrong in saying passing the dep as a function argument is not DI. Search how people do DI in functional programming languages. Often it's passed in as a "context" object.
If you're still not convinced, I took my code and passed it to GPT which responded with this:
"Yes, the trx parameter in your updatePassword function can be considered a form of dependency injection. In this context, trx represents a database transaction object, and by default, it is set to database. By allowing trx to be passed as an argument, you are injecting the dependency (the transaction object) into the function."
@@WebDevCody even so it should be implemented differently. The "injection" means that is the "framework" doing it "automatically"... you are not injecting it, you didnt create an interface nor your are calling "a different class" from the original ...you simply switch from the "database connector" to... "the SAME conction but is an open transaction".. so you cant state this is called DI because a poor.. almost non-existant implementation of it .and.. the CALLER IS ADDING THE DEPENDENCY!. So in not "injected".. you manually are adding it ..
On top of it...you didnt create and interface (and you are using Typescript..so no excuses) and you didnt add the interface type in the parameter...which is basically the main component of DI..so even like this your example. A part of not showing real DI is even very poor example...
PS: ChatGPT while being great.shouldn't (yet) being considerated a safe place to get answer...is a good start for sure an most of them are somewhat correct but..it does still LOTS of mistake so i wouldnt even brag about it as to say "ChatGPT answered this so is correct"
@@WebDevCody
So then a question for you, tell me:
What's the difference on what you did compared to .. passing variable to a function?
@@federicobau8651 the function prior depended on a drizzle db object (implicit interface) which was used from a direct import at the top. Passing it in as a function argument allows any object that extends that implicit interface to be used as the dependency instead. An argument to a function is not a dependency, it’s just a parameter. Importing a third party library or another module from your codebase is a dependency. If the module is dynamically passed in, you’ve done dep injection
@@WebDevCody first you are answering what i asked. Second.. emhh.. "emplicit" for the "interface of a DI"..is totally against what DI is for (one of few) and you really want to have it Explicit.
Third, you are confusing a Dependency...you are calling a dependency "3rd party library" which is dependency of a Project..in DI (and SOLID/Clean code) dependency means..something else..you tottaly can have a something that is a dependecy in you code base..even on the same file XD..dependency here we mean "what the entity depends on" regardless of the source.
Having said that, both your explanation and the code you wrote...while has some components used in DI..Is NOT ENOUGH and doesnt automatically make it DI.
Dont take me wrong, i think your code is correct nor i am suggesting to use a typical DI (which many cases is ..overengineerng) in fact i think is the right thing to do but, nope you can't call that DI..no no no..and then if you have found this info online then..well means is a very confused and misunderstood topic.
If you follow what you explain than...literally ANYTHING that is an object of some sort passed as argument of a function (or constructor) is ..dipendency injection? Common this is Ridículos.
Then why isnt this already called DI on a 5th lesson of a beginner programmer of (most) programming languages when you first study how to declare and call a function? Why is a DI only found in a broader context like a library or lets say a web server?
I tell you why.. because those 2 AREN'T the same thing.
What you have done is pass an object to a function as argument which has a default value. Yes default value is different than the one you passed. Yes both have a method with same name but that ISNT DI.
So you are telling me that, if you have a function called "count" with param fn that calls it as fn.count ..and you pass as argument an array of strings..or an object.. or a custom class thst has a method count..you call that DI :D
..common now..
I love overloading functions, unfortunately in js es no without arg reading
and createTransaction function is created so the service ( use case ) does not know about drizzle right ?
yeah I'm not 100% sure I got the abstraction correct because technically the trx object is a drizzle transactions and the use case can see it and know about it in the callback. I think maybe a better abstraction could be runTransaction([doA, doB]), and behind the scenes the trx object would just inject into the last or first argument.
What colour theme is it
Bearded theme stained blue
I liked this ✍🏽
bruh drizzle is so much better than prisma...
I wanted to also enable this type of dependency injection, but the type of the prisma client and the transaction prisma client aren't the same. That's why It is not Possible to just say (...., db = dbClient) {...} and the call it with ( ...., tx)
[EDIT]: Solution
type DB = Parameters[0];
then use DB type like that ( ..., db: DB = dbClient)
You can't use the following functions from the default Prisma Client: "$connect" | "$disconnect" | "$on" | "$transaction" | "$use" | "$extends"
I mostly use this kind of trick but I never knew that it was called "dependency injection". Now I know the technical word, thanks!
Is not dependency injection..is still passing and argument..dependency injection is very different and has a different endgoal.
Why not just use Nest JS?
Good old transactions always good for endpoints that no 2 users can hit at the same time
it's all about risk mitigation. Will the second call to the database fail? probably not, but if it does, now your system is left in a strange state
@@WebDevCody I used it once on a controller that had a lot of related operations many users can hit the same resource, I ended up with a race condition exception where multiple users try to access a locked row or table even.
You need to handle that case manually with a retry or something.
Usually transactions are good for short operations like 2 fast queries are probably fine.
That's awesome
Why engineers keep creating problems on empty space and complicate everything?
Because it’s fun
Good job babe!
thanks beautiful!
Dear God, he's using JavaScript on the backend.
funny watching this after reading your tweet “hating” on dependency injection
Keep em guessing
@@WebDevCody typical youtuber - explains nothing with a response, just to drive interactions up when otherwise content is quite shit and cannot do it organically
@@ward7576 I hope you have a blessed day
is Saas template will be os ??
yes
Does anyone know what's his theme?
Bearded theme stained blue
Person suggesting this must be from Java background 😄
I hate ORMs with a passion. So much simpler and more powerful to just write SQL.
I know this video is supposed to be about dependency injection and not about the actual use case ....
however when you think about the actual change-password use case, it's probably better to not put this in a transaction if possible. For example, if the business logic allows the user to simply request another password reset token, it's perfectly OK to have deleted the password reset token and then failed to update the password: simply present an error to the user and have them request another password reset token.
Another option is to simply mark the password reset token for deletion without actually deleting it, and have another process come in later and clean up all of the tokens that have been marked for deletion. That way the client can simply retry within some reasonable timeframe (keep in mind that if you do this, you probably want to include information about the current hashed password on the reset token itself so that the same password reset token cannot be used to successfully change a password twice).
In general, my advice is to avoid transactions like the plague. They really slow down the performance of a web server, and usually you can design around the need for transactions by just thinking a bit more about your high-level design. Also transactions inhibit flexibility in the future: for example if you've designed with the assumption of transactions, what happens when you need to put some logic that interacts with services outside the database into a transaction context? Just putting a bit more thought into the high level design to come up with a solution that doesn't use transactions can save you so much headache down the line, and it's really worth it in almost all cases.
I think you are in the wrong path. Even think that transaction can slow performance down is...not that correct.
Transactions ensure consistency and correctness. Just read what you wrote and it confirms this.
You would avoid use transaction for "some weird reason" and possibly ending up..do a clean up later?😂 that's crazy.
This is not dependency injection, wtf 🤣
Java dev spotted
My eyes are bleeding
Maybe see your dr about that one
@@WebDevCody my doctor can't help with your code, I don't think anyone can... posting a video where you shit on SOLID and call it dependency injection...
@@saburex2 getting away from OOP is the first dosage of medicine
This is poor man's dependency injection. Real programmer's use DI containers.
theme name?
bearded theme stained blue
@@WebDevCody thanks
Huh I thought it's pronounced Lu-Chia
🤷♂️ no clue
The Italian pronunciation is luchia, so it depends.
Owww what is your vscode theme?🥹