3 ms·
I won’t say whether it’s better, that depends on a lot of things. But the point is inversion of control[1]. Some say DI promotes separation of concerns and modu
by eyelidlessness 5y ago
I won’t say whether it’s better, that depends on a lot of things. But the point is inversion of control[1]. Some say DI promotes separation of concerns and modularity, or improves testability by making substitution easier.
To me the value depends a lot on circumstances like overall complexity, likelihood of change, availability of comparable test substitution mechanisms… and more than anything magic. I don’t mind direct DI—as in, passing dependencies to things. But I generally dislike magic DI where some framework passes dependencies based on some config or usage pattern. Those frameworks are often difficult to debug and more trouble than they’re worth.
1: https://www.martinfowler.com/articles/injection.html https://www.martinfowler.com/articles/injection.html
- horsawlarway 5y agoI agree 100% on avoiding the "magic". Particularly since implementing DI in frameworks like express/koa is already drop dead simple - literally just have your first middleware add your services to the request context (Side note - this works just fine with TS, including nice auto-complete and type-hinting).
- rektide 5y agoAs someone whose walked through a lot of code-bases, I rather hate the "unmagic" just-write-whatever-nest-of-route.js-files-you-want path. It's easy to write routes in express, yes. But every company & usually every project in the company usually does it differently. Oh this person used a glob tool to find all the route.js files. Oh this person has nested route.js files littered about. Oh this project is just a massive ultra-complicated routes.js. A huge part of the win of DI & places like Java JAX-RS is that code one place looks fairly alike code another place. Dependency Injection frees us from having to know a project well, from having to be familiar with structure; we simply ask or tell, & there's a system there. I find it elegant that Express routing concerns, React concerns, and all the various configuration/providing systems you might need can be managed via IoC. Recent TypeScript example I found of these concerns, in Injex: https://www.injex.dev/docs/plugins/express#get-post-patch-put-del https://www.injex.dev/docs/plugins/express#get-post-patch-pu... https://www.injex.dev/docs/plugins/react#the-renderinjexprovider-method https://www.injex.dev/docs/plugins/react#the-renderinjexprov... There's a lot of push back against "magic", but really I think it's something that served Java extremely fantastically well, particularly the Dependency Injection/IoC systems. I think developers of those systems got a ton of value, and it was really understated just how noble & simple those worlds were, how clean & easy it was to walk up to any random controller & be able to discern more or less what data was going to get used, in what scopes. By not having all that stuff written in the ad-hoc, existentially open world of declarative code, and instead encoded as normative, regularized, low-overhead annotations, there was a kind of predictability & visualizability & simplicity that I think a lot of these practitioners probably sorely miss, now that they're probably mostly writing Node.js like nearly everyone else. ;)
- twistedpair 5y agoWith great power comes great responsibility. I've seen Spring projects where DI was a total rat nest of hundreds of XML wireup config files. Downhill with a tailwind, it just might boot correctly, after minutes classpath scanning for dependencies. Use DI judiciously where it provides the most value. Type, if you can type it (e.g. I vastly prefer explicit wireup in Guice Module.java files over Spring's "stringly typed" XML files. Type checking is more likely to catch classpath errors and typos. Event better, use compile time macro wireup like Scala's Macwire.
- rektide 5y agoIn the manage your objects/entities vs don't manage your objects entities (just code) struggle, I strongly advise the opposite for most folk: figure out better containerizations of control for your entities/objects. There's plenty of bad examples of ALL good & popular technologies. Don't be afraid, don't believe the terror being sold to you: give it a go, explore, see what value there is.
- eyelidlessness 5y agoI don’t like to reply to an agreement with a disagreement, but I find Express-style middleware exactly the kind of magic and type-safety defeating stuff I prefer to avoid. You can surely make it safer and more explicit by, for instance, creating a new wrapper for your Router or whatever. But by default the interface promotes casting/coercion of Request etc and just blindly asserting that you’re sure the middleware you’re expecting is attached. In the past, I’ve worked around this by wrapping Express to make any middleware dependencies explicit on each route, and by making “middleware” actually just pure functions which return whatever state they derived from the Request. Essentially a Route just becomes a pipeline of composable functions. It worked very well, but in hindsight I found I got almost no value out of Express itself at that point.
- ptx 5y ago> direct DI—as in, passing dependencies to things Mark Seemann called it "poor man's DI" in the first edition of Dependency Injection in .NET and changed it to "pure DI" in the second edition, but "direct DI" might be a better term.
- eyelidlessness 5y agoThinking on it further I want to give credit to one of the two major influences on my preference: 1. This one: “explicit is better than implicit” as a principle 2. Not this one: functional programming “Pure DI” presumably insinuates the latter, but can get rather muddy (is dynamic scope “pure?” lololol) and I think calling it “explicit DI” might be more accessible and easier to understand.
- twistedpair 5y agoCommon use cases: if DI can inject different implementations, you can have a PostgresDaoImpl and SqliteDaoImpl. The DI config for running in prod uses all the Postgres implementations, while the impl for running unit tests locally uses the Sqlite impl. Your code stays clean, because nothing on the consumer side needs to change if in prod/test or postgres/sqlite. Better still, your code doesn't even need awareness that prod/test environments even exist. Keeps things simpler in the long run.
- eyelidlessness 5y agoThis is really common usage, but I find it somewhat baffling. Once adopted, you have to either: - Refrain from using any Postgres features that don’t work [the same way] in SQLite (so much for your code not knowing about your test environment!) - Maintain both in tandem and, presumably, test that they have the same behavior or… accept that they may not; in either case you have to accept that you might not know if your SQLite implementation is correct I don’t think the alternative is having code which knows about the test environment. Instead, it’s: test with the real service or set of services you use, and limit the surface area under test. That doesn’t mean you can’t use DI! It just means you have: - data access layer tests that hit Postgres (and any other supported provider) - unit tests of anything consuming the data access layer, injecting a provider which only produces results consistent with the behavior already tested in the data access layer At that point, why inject SQLite? You already ~know the results you can expect from the injected service… just return them.
- yasserf 5y agoI agree with the child comment that using different databases can hide away special database features (although ORMs unfortunately seem to be doing that anyways). However I completely agree with what what's really important is the different types of services you run based on the implementation. So for example when I develop my applications completely off grid, I use express + reaper to mimic S3 / cloud storage. When I deploy just swap it out with the AWS S3 service instead. I also got inspired to use this pattern when building deepstream.io, it's a node server that can take any database/cache/message bus/etc and pretty much run with it (with a small library adaptor). In that case we basically say 'the minimum requirement for functionality is XYZ and if the service provides is, we are in. Super useful.