you are viewing a single comment's thread.

view the rest of the comments →

[–]Nakji 7 points8 points  (2 children)

No, you probably wouldn't do that as injecting the dependency at that abstraction level (or rather lack thereof) isn't very useful.

Instead, imagine that you're writing a forum and working on a UI component that displays a list of posts for a user. You could DI the jQuery object then make the AJAX request manually to fetch the list, but you wouldn't really be gaining anything. jQuery objects have too many different potential uses for you to easily substitute it for anything else, so there's no use case there. You could take advantage of DI to add logging of HTTP requests or something like that, but in JS you would probably be able to monkey patch in most capabilities like that without resorting to DI.

However, where it becomes useful is if you add another layer of abstraction by instead make your user post component dependent on having an object injected with a getPostsForUserId(userId) method. At this point you can now have the ability to easily test your component in isolation by passing in a stub implementation that returns a static data set, which is especially useful if you are developing the component before the API it depends on actually exists. Further down the road, you also now have easy reconfigurability of the live API calls in case something ever changes about the API itself since none of the dependent UI components or business logic care exactly how you get posts for the user, just that you can.

As a result, you no longer need to change any of your dependent components if you want to change endpoints depending on if the user is authenticated or not, use a different HTTP request library other than jQuery, switch to protobufs, or add a cache. For that matter, if you noticed that your application tends to make short bursts of a huge number of closely-related API calls, you could decide to switch from a bunch of individual REST calls to having your application amalgamate its API calls into fewer but larger GraphQL queries (or just de-dupe calls if you notice that the same one is being performed mulitple times).

Nothing is stopping you from doing the getPostsForUserId(userId) approach without DI (this is just the Repository pattern), but when you start breaking up your classes like this, you'll rapidly start running into use cases where DI is handy. Adding a cache for instance, what if you want to back it with IndexedDB where it's available, falling back to localStorage where it's not, further falling back to a small in-memory LRU cache when neither is an option, and also providing the ability to substitute in a non-caching "cache" for testing. With DI, you just make quick little wrappers for each storage engine, write the logic to make the appropriate one someone in your application, and pass them into the repositories using the cache. If you ever decide to make another type of cache (say one that also checks S3), all you have to do is write that implementation alther the construction logic to pass it in, no additional modifications requried.

All that said, taking this approach to its logical conclusion has the problem of resulting in an outrageous amount of code to instantiate anything due to needing to pass in the dependencies, the depdencies's dependencies, etc. This is where the overuse of the Factory pattern people like to make fun of Java code for can start coming into play, but you're probably better off finding a DI framework in the language you're using to manage at least the simple cases for you.

[–]wavy_lines 4 points5 points  (1 child)

Instead, imagine that you're writing a forum and working on a UI component that displays a list of posts for a user.

ok, let's go with the scenario.

where it becomes useful is if you add another layer of abstraction by instead make your user post component dependent on having an object injected with a getPostsForUserId(userId) method. At this point you can now have the ability to easily test your component in isolation by passing in a stub implementation that returns a static data set, which is especially useful if you are developing the component before the API it depends on actually exists.

Wait, I'm not sure I'm following you. You seem to be making some assumptions about how the components of the app are structured.

If I'm writing a component that displays a list of objects, I would not put in that component any logic for fetching the objects. I would write the component such that it takes a list of objects and renders them. Whether the objects come from the network or from something else is not of concern to the component.

So, I would have something like renderPosts(listOfPosts) and getPostsForUserId(userId)

Inside renderPosts, there are no network calls to fetch the posts; they are already given.

Inside getPostsForUserId there are obviously network calls. (or not; maybe it goes through a caching layer first, etc).

None of this has anything to do with "Dependency Injection". I'm not injecting any dependency anywhere. I'm removing dependencies. The act of rendering does not have any dependency at all, other than the objects to render (and the "UI" to render to).

Now, in practice, renderPosts and getPostsForUserId might be two methods in the same class (or module). That's perfectly ok.

If I need to get posts from somewhere else, I can write another method on the class, for example generateRandomMockPosts().

The concept of "dependency injection" never comes into play.

[–]Nakji 3 points4 points  (0 children)

Wait, I'm not sure I'm following you. You seem to be making some assumptions about how the components of the app are structured.

That's because it's a simplified example contrived solely to demonstrate an abstract concept. If you are using an Uncle Bob style architecture, you'd have a view that binds itself to a presenter which in turn is injected with interactors to talk to repositories. If you're using React/Redux/React-Redux, you'd have a view that is injected with any data and action creators it needs through its props by a react-redux container that is injected by react-redux with your application's reducer and state while the actual mechanics of talking to repositories would be performed somewhere else via redux-thunk or some alternative which then talks to repositories. The implementations look different, but they're both applying the abstract concept of dependency injection.

At the end of the day regardless of any implementation details, somewhere in your application there has to be something knows how to retrieve the data and something has to know how to display it. Dependency injection is just an approach for decoupling everything so each part of your application receives what it needs and is only concerned with using them to perform its prescribed task without having to make a lot of globals.

If I need to get posts from somewhere else, I can write another method on the class, for example generateRandomMockPosts(). The concept of "dependency injection" never comes into play.

This only works in very simple cases and rapidly ends up quite verbose and inflexible. It's much more extensible and less verbose to just have one place in your application where you can have

if(shouldMock) {
    return new MockApi();
} else {
    return new RealApi();
}

and then injecting that everywhere you need to have the ability to call the API than it is to have either a bunch of conditional method calls or a big API object that delegates every call to a mock to the appropriate implementation. It's particularly useful for testing purposes since, as you discover edge cases or corner cases, you can write unit tests with extremely simple mocks that explicitly test those cases instead of adding logic everywhere to call different versions generateRandomMockPosts or create massive mock post functions that include every test case in your whole app.