What is dependency injection and when should I realistically use it
Recently I've got some critics from people saying I should not create a Helper / Utils class with a bunch of public static methods and that I should rather use DI practices for it
For me my approach makes sense since my Utils are only supposed to exist once in the whole program so I can access it from everywhere
So what exactly is bad about my approach?
10 Replies
im going to make a few assumptions here, so if i am wrong in anything please dont feel offended.
- if you ask this question it sounds like also things like SOLID are not much known. so this seems not to be a partial issue, but a more breader from i think lacking experience.(DI alone might not change much in terms of impovement for you)
- if you create code that is structured and uses DI you can propperly test your stuff. (a static function call can not be mocked, but if you inject a service (interface!!! ) you then in unit test can mock that external service call.
- using static methods/classes binds everything to that implementation. forever.
thats a small cut of how i see this.
feel free to ask if i didnt explain something clear.
There is a time and a place for static utility methods. Specifically, if they have no dependencies at all and are entirely stateless, they should probably just stay a static method.
However, if your helper/utility has some dependencies, meaning other objects it relies on for some reason (maybe for writing to a stream, or sending something to an external http service) thats when you might want to refactor and turn your helper into a service you can inject via DI
thats because the dependency for that service can then also be injected, and you have all your object/dependency lifetime management in one place
My Utils classes mostly only contain things like strings (for example a base URL), paths or things like conversion functions
thats probably fine then
if the base url has no need to change, its perfectly fine to have that in a
const string
somewhereReading over this again:
So you say a class or collection of functions that for instance makes API calls should not be static
That's my approach, I usually make API Client classes, each of them have their own headers, own Auth keys or whatever the api needs and their own public functions that only work in an instance so each client is only concerned with its own calls
Wouldn't this Client object describe DI as in what you said about the Http thing?
you certainly shouldn't have your API key as a
const
/static readonly
also, a method that does an API call needs a HttpClient
instance. If the method is static, where are you getting that from?thats why each API Client is holding its own HttpClient
Not the worst idea ever, but also not how you'd see that implemented most of the time
since a HttpClient is an object with internal dependencies and non-trivial lifetime, I'd probably recommend injecting it through HttpClientFactory (with
Microsoft.Extensions.Http
)
but a static httpclient works okay, especially if you only have a very low number of these API clientsStatic methods are absolutely the way to implement things, unless you feel there is a need to be able to depend on "something that does X", except that you don't want it to actually do X. This in particular comes up during testing.
For instance, if you have a method that says "File.WriteAllText", that writes a string to a file, replacing it if it exists, and you want your own code to depend on "something that (re)writes a file with the text I supply", then for test-purposes, using a static method call is not a good idea, as you will have to depend on an implementation that actually writes something to disk.
Instead, that might warrant an abstraction, and interface or class that you can depend on that will write to the file on disk, but which you can substitute during testing with an implementation that does not write to the disk, but instead writes to memory, or just observes that the write was attempted.
100% agree, I refer to such things as Egresses now, and then how Data comes in as the Ingress.
Egresses would be stuff like:
1. Writing out to a file
2. Invoking an HttpClient
3. Sending packets to a Udp server
Luckily a few of these things have very easy ways to mock them without extra effort. HttpClient has a built in way to do it, you can just use a StreamWriter you write to for the filestream (and instead pass in a memory stream in your test), etc etc
For Ingress that's pretty much just the oarams of your method you are testing which is easy as well.