I’ve been working primarily in Go for the past five years, including some extremely complex projects, and I have never once wished I had dependency injection. It has been wonderful. I have used dependency injection - previously I worked on a C# project for years, and that used DI - but I adore Go’s simplicity and I never want to use anything else (except for JS for UI, via Electron or Wails for desktop).
Edit: If we’re talking about dependency injection in the general sense (separation of concerns, modularization, loose coupling), then yeah I agree that’s kind of critical to writing good, maintainable software. When I hear “dependency injection” I think of frameworks such as Unity, and that is what I was specifically talking about - I am very happy with the fact that I have felt zero need to use any framework like that over the last five years.
Go programmer here: What does Go’s simplicity have to do with dependency injection? What does a language itself have to do with dependency injection?
Reading your post and not being personally familiar with your work, I do wonder, perhaps your “extremely complex projects” wouldn’t be so extremely complex if you practiced dependency injection?
How do you unit test your extremely complex projects if your business logic carries the additional responsibility of creating objects?
What does Go’s simplicity have to do with dependency injection?
In my experience, following Go’s philosophy of simple solutions eliminates the need for complex solutions such as dependency injection.
How do you unit test your extremely complex projects if your business logic carries the additional responsibility of creating objects?
I write modular code that accepts interfaces so I can test the components I want to test. The vast majority of object creation happens at initialization time, not in the business logic. For the projects I’ve worked on, that would be true with or without DI - I don’t see how that’s relevant.
perhaps your “extremely complex projects” wouldn’t be so extremely complex if you practiced dependency injection?
When the CTO says, “Make it distributed and sharded,” I do what I’m told, but that is an intrinsically complex problem. The complexity is in the overall behavior of the system. If you zoom in to the individual execution units, the business logic is relatively simple. But the behavior of the system as a whole is rather complex, and DI isn’t going to change that.
Edit: I was interpreting “using DI” to mean using a DI framework such as Unity, and I would be happy to never need one of those frameworks ever again.
Maybe I’m misunderstanding what “dependency injection” means. When I hear “dependency injection” I think of a DI framework such as Unity, so I thought “using DI” meant using one of those frameworks.
I say it’s all about data flow and composability, if it’s pretty much always in one direction (modular tree structure/architecture) then you just don’t need all these “patterns”…
(I think Go takes all mediocre language features together and makes an even more mediocre language TBH, take error handling for example, or generic programming (which I agree should be used sparingly, but is super useful if you need it))
I’ve heard of Rust. It sounds noisy and even more verbose than Go, which is already a fairly verbose language. I haven’t had any reason to learn Rust, so I haven’t done so. The error handling is annoying but at this point I don’t really notice it any more. And as interolivary said, Go has generics now.
Yeah this was my initial reaction way back when I first heard of Rust as well (sometime around 2015 or so I think). TBF it’s definitely not on the same level as e.g. Haskell. But it’s generally I would say less verbose than go (or at least has verboseness where it makes sense compared to go IMHO).
The generic system is also (way) less powerful compared to Rusts (The trait type system/type-classes is really a nice Haskell-inspired thing, that I don’t want to miss anymore). Also the lack of sum types and proper pattern matching makes go more verbose IMHO.
*grumble*. I dabbled in Scala a few years back and I am really grumpy every time I remember that Go doesn’t have sum types, pattern matching, and the closed union of types construction you can create with an abstract final class in Scala. I loved that last one and used the heck out of it. I would love to have a compiler-enforced guarantee that a set of types was closed and could not be extended.
I mean unit tests. I work on Spring Boot apps where there are distinct layers (controller -> service -> persistence), and you generally inject mocks into your object to isolate tests to the specific code you want under test. One benefit of this approach is that it’s pretty easy to get 90% coverage.
I still haven’t really understood the use (and use case) of “dependency injection” (and it feels to me I read now everything about dependency injection I could find), to me it seems to be yet another ProblemFactory.
Single responsibility principle: is your GetData() function responsible for getting data? Or is it responsible for creating a new database connection and also using that to go get the data?
Start naming your functions by what they really do. When you see the word “and” in your function name, you know your function is responsible for too much.
Dependency injection is the difference between CreateDatabaseConnectionAndGetData() and GetData(connection ConnectionType).
In the first example, that function will always connect to the specific db that you hard coded in it. It probably has to also read in a config file to get the connection details. Maybe you should name it ReadConfigAndCreateDatabaseConnectionAndGetData()?
In the second example, I can pass in a MySQL connection or PostgreSQL connection, or some dummy connection for testing.
Keep all that nasty dirty untestable code in one place and spare your business logic from owning all of that.
Thanks for the write up, but as I said, I know and I’ve read all about that already. I still cannot see, why a simple function argument and an interface isn’t enough (you can probably already call that “dependency injection” if you want to get fancy)
I guess I have just divorced with OOP and the “necessary” “design patterns”…
Things are more simple and less boilerplaty now for me :).
I’m just lurking around, so don’t mind me. But I gotta say, it really does sound like y’all are just making shit up sometimes lol. Like a mechanic trying to charge you an extra 50 bucks because your jindle shaft needed an alignment
You’re gonna have a tough time talking to others about your code if you don’t agree on common terminology. Function invocation is just function invocation, it doesn’t say anything about the form of the parameters or composition. Dependency injection is a well known and commonly understood method of facilitating decoupling and composition by passing a function’s dependencies as parameters. From your comments you’re talking about the second, but refusing the name, for… reasons?
I guess I’m a little bit too long already in the functional/data-driven world (after being a decade in OO languages (IMO too long…)). In OOP you may need a separate term for that.
But I think it’ just not really necessary in functional programming, it’s just another parameter when calling a function, that may be a somewhat type-constrained generic (for testing e.g. with a mock implementation).
I mean function parameters are somewhat dependencies anyway, so should I call all my parameters now dependencies and invocation “injection”?
Thought you were OP for a second there, as they were talking about composability. Whether it’s dependency injection or not depends on what shape your parameters take. If you’re doing functional programming and you’re passing handlers and connections etc. as params, that’s dependency injection. If you’re only passing strings and objects and such and the function has to do a bunch of logic to decide how to handle its params, that’s not dependency injection.
“Dependency injection” is just a term for providing a function or method with its dependencies rather than making the function go and gather them itself.
It’s (typically) done through parameters, but it’s still more specific than just invoking a function. It describes how that function was written and the reasoning for certain parameters. To the other commenter’s point, you’ll have a hard time communicating about your code with other developers if you refuse to use the term dependency injection just because you don’t like OOP.
Why is the joke with Java always factories? Factories are really super useful in a dependency injection context.
I’ve been working primarily in Go for the past five years, including some extremely complex projects, and I have never once wished I had dependency injection. It has been wonderful. I have used dependency injection - previously I worked on a C# project for years, and that used DI - but I adore Go’s simplicity and I never want to use anything else (except for JS for UI, via Electron or Wails for desktop).
Edit: If we’re talking about dependency injection in the general sense (separation of concerns, modularization, loose coupling), then yeah I agree that’s kind of critical to writing good, maintainable software. When I hear “dependency injection” I think of frameworks such as Unity, and that is what I was specifically talking about - I am very happy with the fact that I have felt zero need to use any framework like that over the last five years.
Go programmer here: What does Go’s simplicity have to do with dependency injection? What does a language itself have to do with dependency injection?
Reading your post and not being personally familiar with your work, I do wonder, perhaps your “extremely complex projects” wouldn’t be so extremely complex if you practiced dependency injection?
How do you unit test your extremely complex projects if your business logic carries the additional responsibility of creating objects?
In my experience, following Go’s philosophy of simple solutions eliminates the need for complex solutions such as dependency injection.
I write modular code that accepts interfaces so I can test the components I want to test. The vast majority of object creation happens at initialization time, not in the business logic. For the projects I’ve worked on, that would be true with or without DI - I don’t see how that’s relevant.
When the CTO says, “Make it distributed and sharded,” I do what I’m told, but that is an intrinsically complex problem. The complexity is in the overall behavior of the system. If you zoom in to the individual execution units, the business logic is relatively simple. But the behavior of the system as a whole is rather complex, and DI isn’t going to change that.
Edit: I was interpreting “using DI” to mean using a DI framework such as Unity, and I would be happy to never need one of those frameworks ever again.
basically dependency injection
Maybe I’m misunderstanding what “dependency injection” means. When I hear “dependency injection” I think of a DI framework such as Unity, so I thought “using DI” meant using one of those frameworks.
I say it’s all about data flow and composability, if it’s pretty much always in one direction (modular tree structure/architecture) then you just don’t need all these “patterns”…
… until you’ve heard of Rust :)
(I think Go takes all mediocre language features together and makes an even more mediocre language TBH, take error handling for example, or generic programming (which I agree should be used sparingly, but is super useful if you need it))
I’ve heard of Rust. It sounds noisy and even more verbose than Go, which is already a fairly verbose language. I haven’t had any reason to learn Rust, so I haven’t done so. The error handling is annoying but at this point I don’t really notice it any more. And as interolivary said, Go has generics now.
Yeah this was my initial reaction way back when I first heard of Rust as well (sometime around 2015 or so I think). TBF it’s definitely not on the same level as e.g. Haskell. But it’s generally I would say less verbose than go (or at least has verboseness where it makes sense compared to go IMHO).
A good article about this: https://matklad.github.io/2023/01/26/rusts-ugly-syntax.html
The generic system is also (way) less powerful compared to Rusts (The trait type system/type-classes is really a nice Haskell-inspired thing, that I don’t want to miss anymore). Also the lack of sum types and proper pattern matching makes go more verbose IMHO.
*grumble*. I dabbled in Scala a few years back and I am really grumpy every time I remember that Go doesn’t have sum types, pattern matching, and the closed union of types construction you can create with an abstract final class in Scala. I loved that last one and used the heck out of it. I would love to have a compiler-enforced guarantee that a set of types was closed and could not be extended.
How do you write tests?
How does dependency injection have anything to do with writing tests? I write tests by writing a test function that executes the code I want to test…
I mean unit tests. I work on Spring Boot apps where there are distinct layers (controller -> service -> persistence), and you generally inject mocks into your object to isolate tests to the specific code you want under test. One benefit of this approach is that it’s pretty easy to get 90% coverage.
Do you write unit tests with objects mocked via interfaces? Or polymorphism via interfaces? Those are the main reasons to use DI.
I guess I have to start calling function invocation with generic parameters, fancy names (like “dependency injection” ^^)
Relevant:
I also prefer the simpler explanation.
https://www.jamesshore.com/v2/blog/2006/dependency-injection-demystified
As I read somewhere:
I use various strategies depending on what seems appropriate, including the two you mention. I’ve never felt the lack of DI.
I still haven’t really understood the use (and use case) of “dependency injection” (and it feels to me I read now everything about dependency injection I could find), to me it seems to be yet another
ProblemFactory
.Single responsibility principle: is your
GetData()
function responsible for getting data? Or is it responsible for creating a new database connection and also using that to go get the data?Start naming your functions by what they really do. When you see the word “and” in your function name, you know your function is responsible for too much.
Dependency injection is the difference between
CreateDatabaseConnectionAndGetData()
andGetData(connection ConnectionType)
.In the first example, that function will always connect to the specific db that you hard coded in it. It probably has to also read in a config file to get the connection details. Maybe you should name it
ReadConfigAndCreateDatabaseConnectionAndGetData()
?In the second example, I can pass in a MySQL connection or PostgreSQL connection, or some dummy connection for testing.
Keep all that nasty dirty untestable code in one place and spare your business logic from owning all of that.
Thanks for the write up, but as I said, I know and I’ve read all about that already. I still cannot see, why a simple function argument and an interface isn’t enough (you can probably already call that “dependency injection” if you want to get fancy)
I guess I have just divorced with OOP and the “necessary” “design patterns”…
Things are more simple and less boilerplaty now for me :).
My brother in Christ, that is dependency injection. Just because you don’t want to call the spade a spade anymore doesn’t make it not so.
Yeah I guess you can call it like that, I’ll just call it function invocation…
Di is just good functional practice. I’m not sure it’s a super importent idea to someone who knows how to write a good function.
Edit: “a function should do one thing and its operands should be passed as arguments” for the OO world.
I’m just lurking around, so don’t mind me. But I gotta say, it really does sound like y’all are just making shit up sometimes lol. Like a mechanic trying to charge you an extra 50 bucks because your jindle shaft needed an alignment
You’re gonna have a tough time talking to others about your code if you don’t agree on common terminology. Function invocation is just function invocation, it doesn’t say anything about the form of the parameters or composition. Dependency injection is a well known and commonly understood method of facilitating decoupling and composition by passing a function’s dependencies as parameters. From your comments you’re talking about the second, but refusing the name, for… reasons?
I guess I’m a little bit too long already in the functional/data-driven world (after being a decade in OO languages (IMO too long…)). In OOP you may need a separate term for that.
But I think it’ just not really necessary in functional programming, it’s just another parameter when calling a function, that may be a somewhat type-constrained generic (for testing e.g. with a mock implementation).
I mean function parameters are somewhat dependencies anyway, so should I call all my parameters now dependencies and invocation “injection”?
Thought you were OP for a second there, as they were talking about composability. Whether it’s dependency injection or not depends on what shape your parameters take. If you’re doing functional programming and you’re passing handlers and connections etc. as params, that’s dependency injection. If you’re only passing strings and objects and such and the function has to do a bunch of logic to decide how to handle its params, that’s not dependency injection.
“Dependency injection” is just a term for providing a function or method with its dependencies rather than making the function go and gather them itself.
It’s (typically) done through parameters, but it’s still more specific than just invoking a function. It describes how that function was written and the reasoning for certain parameters. To the other commenter’s point, you’ll have a hard time communicating about your code with other developers if you refuse to use the term dependency injection just because you don’t like OOP.