GithubHelp home page GithubHelp logo

Wouldn't it be ok to directly import well-known functions, such as pipe, in our code instead of injecting it? about comments-api HOT 10 OPEN

dev-mastery avatar dev-mastery commented on July 17, 2024 6
Wouldn't it be ok to directly import well-known functions, such as pipe, in our code instead of injecting it?

from comments-api.

Comments (10)

arcdev1 avatar arcdev1 commented on July 17, 2024 14

Hi @hbarcelos. Thanks for the feedback. Your last paragraph really hits the nail on the head. The point of the video and the accompanying code is to teach people the "rules" and like anything, once you master the "rules" you start finding reasonable places to break them.

There's a reasonable argument to be made for just importing most, if not all, the dependencies of isQuestionable. Ultimately, the Dependency Inversion Principle (and all of the Clean Architecture model and SOLID principles) are about managing risk. In the context of software development when we talk about risk, we're talking about the likelihood and impact of future change. In the last few years, I've developed a mental model of risk that looks like this:

likelihood of change x impact of change = degree of risk

The higher the risk, the more likely I am to take steps to mitigate that risk. In the specific case you mentioned, the risk is relatively low so the dependency injection is probably overkill.

The other thing to keep in mind is that there is a direct correlation between the size of your codebase (and team) and the impact of change. As your app and team grow the impact of changes increases, so you need to re-calculate the risk and refactor accordingly.

With respect to libraries like axios-mock-adapter I tend to be extremely wary of such dependencies because they are maintained independently of axios and yet tightly coupled to axios which tends to be a toxic combination.

I try to avoid mocks as much as possible. When unit testing http requests I tend to decouple the request itself from the code that builds the request and the code that processes the response, that way I can unit test those independently and then write an e2e test that does the real http request. This eliminates the need for any mocking. See buildModerationApiCommand and normalizeModerationApiResponse for example. These are pure functions that are easy and fast to test.

from comments-api.

arcdev1 avatar arcdev1 commented on July 17, 2024 4

Thanks for writing such a thoughtful response @hbarcelos . I really enjoy these kinds of conversations.

In terms of how to decide when to define our own interfaces, I look at it this way. We can divide the features or capabilities of our app into two categories: commodity features and differentiating features.

Commodity features are those features which are common to most, if not all, applications (e.g. logging, error handling, network communication, database communication, routing...).

Differentiating features are those features which are unique to our application. Most of the time, the differentiating features are directly related to the value proposition of our application and in commercial software, the differentiating features are what customers are paying for.

When it comes to commodity features, there is little value in trying to define our own Interface. Ideally, we would adopt an open standard and prefer libraries that conform to that standard, of course the problem with standards is there's almost never just one:

How standards proliferate via xkcd

So, we usually end up picking a popular library (e.g. axios) and just adopting its interface knowing that, if we need to swap it out in the future, we can write an adapter that maps the new Interface to the Interface our module is expecting. Of course, this is easier to do if we used DI from the start.

For differentiating features, it's another story. I would argue there is always value in defining our own Interfaces here. That's why I'm generally quite resistant to the idea of using a third party Framework to build my Entities or Use Cases. For example, I would never use a Mongoose Model as a Business Entity, nor would I use Mongoose to enforce business rules. If I used something like Mongoose, the Models it generates would only be used to interact with the database and would be kept far, far away from my Business Logic.

from comments-api.

arcdev1 avatar arcdev1 commented on July 17, 2024 4

Reopening so that it’s easier to discover this conversation.

from comments-api.

hbarcelos avatar hbarcelos commented on July 17, 2024 3

likelihood of change x impact of change = degree of risk

I liked this! I think it's something I already do, but not deliberately. That's a good mental model to have, so thanks for that πŸ˜ƒ.

About axios-mock-adapter, I must say I agree with you. I myself tend to follow the same approach most of the time. Usually I only mock when it's inevitable (e.g.: when calling a paid or limited-usage service). However, I do that in a lower level, using nock, just to avoid the kind of hassle you've mentioned.

I only brought that up because it's usually an argument people make:

Oh, if you don't inject dependencies, you cannot/it's too difficult to test!

While this might be true for other less dynamic languages, JavaScript gives you a lot of freedom.


The other thing to keep in mind is that there is a direct correlation between the size of your codebase (and team) and the impact of change. As your app and team grow the impact of changes increases, so you need to re-calculate the risk and refactor accordingly.

I agree. However this is what makes it difficult to know exactly when you need to define your "interfaces" in the inner circles and implement them in the adjacent ones.

For example, I believe it would be completely unnecessary having to create an interface for an HTTP Client in our controllers and then write adapters for axios, request, got and so on. Usually it's better just to choose one of them and stick to it.

However, problems will arise if the chosen module somehow has a severe security breach that can't/won't be fixed anytime soon. Then you're going to have a bad time trying adapt everything to the replacement module's API.

For example, your normalizeModerationApiResponse is coupled to axios API, because it assumes the response has a data property:

export function normalizeModerationApiResponse (response) {
  return (
    !response.data.Classification ||
    response.data.Classification.ReviewRecommended
  )
}

If we switch to request-promise or got, the property will change to body. So we'd have to remember changing this function as well to make that work. Imagine now the code-base is huge and there are many normalizeModerationApiResponse-like functions. Chances of forgetting to update one of those are pretty high (hence the need for a good E2E test suite).

When it comes to that, probably what we'd have to do in such case is write an adapter from axios API to request-promise or got. The problem now is that we end-up with the same problem axios-mock-adapter has,

[...] because they are maintained independently of axios and yet tightly coupled to axios which tends to be a toxic combination.

There is another problem regarding request-promise. Its default behavior is to return only the response body for successful requests, so we will have to remember to properly configure it with the resolveWithFullResponse option.

Damn! These things are really hard πŸ˜….

from comments-api.

hbarcelos avatar hbarcelos commented on July 17, 2024

@arcdev1 thank you very much for taking the time to discuss this matter with me. Really appreciate it.

So, we usually end up picking a popular library (e.g. axios) and just adopting its interface knowing that, if we need to swap it out in the future, we can write an adapter that maps the new Interface to the Interface our module is expecting. Of course, this is easier to do if we used DI from the start.

I tend to use a slightly different approach, which I call the symlink.
Whenever I need something like an HTTP client, I define a file in a common folder (e.g.: src/shared) and do as follows:

// src/shared/http-client.js
module.exports = require('axios');
// or
export { default } from 'axios';

Now all files depending on an HTTP client will require this module instead of requiring axios directly.
If I ever need to change axios into something else, I can change the dependency here and export the adapter. I don't even need to touch another module.

This way I don't need to bother with injecting dependencies.


That's why I'm generally quite resistant to the idea of using a third party Framework to build my Entities or Use Cases. For example, I would never use a Mongoose Model as a Business Entity, nor would I use Mongoose to enforce business rules.

πŸ™ πŸ™ πŸ™ Amen to that!

There has been more occasions than I would like to count where I tried to explain that to colleagues only to hear back "Oh, ok! But doing this with mongoose is easier" πŸ€¦β€β™‚οΈ

If I used something like Mongoose, the Models it generates would only be used to interact with the database and would be kept far, far away from my Business Logic.

Another problem is that if you ever decide that is better to ditch Mongo, you just can't. You are stuck with it forever, unless you do a full rewrite of your application (which will probably go south at some point).

That's why I believe 90% of what people call vendor lock-in are just poor architectural decisions :v.


Anyway, to not extend this discussion here any further, I'll close this issue.

Do you happen to know any place we could have these kind of high level discussions? Slack? Gitter? Anything?

from comments-api.

arcdev1 avatar arcdev1 commented on July 17, 2024

Do you happen to know any place we could have these kind of high level discussions? Slack? Gitter? Anything?

If you've subscribed to my newsletter (devmastery.com) you'll see an invite to our Discord Server in my regular email blasts and I'm happy to have conversations exactly like this on there.

You'll also find a couple hundred devs there with different backgrounds and experience so it would be a great place to start this kind of chat.

from comments-api.

hbarcelos avatar hbarcelos commented on July 17, 2024

Niice... that's sound very interesting.
I just subscribed to your newsletter.

There is a little bug in it though:

After clicking the confirmation email link, I'm redirected to https://devmastery.com/signup/thank-you.html. This gives me a 404 error.

from comments-api.

arcdev1 avatar arcdev1 commented on July 17, 2024

Thanks for the heads up on the bug. Will fix.

from comments-api.

Ryuno-Ki avatar Ryuno-Ki commented on July 17, 2024

However, problems will arise if the chosen module somehow has a severe security breach that can't/won't be fixed anytime soon. Then you're going to have a bad time trying adapt everything to the replacement module's API.

axios/axios#2131 *cough

Now all files depending on an HTTP client will require this module instead of requiring axios directly.
If I ever need to change axios into something else, I can change the dependency here and export the adapter. I don't even need to touch another module.

This way I don't need to bother with injecting dependencies.

Oh, I saw this approach somewhere … can't find it right now … the idea was to have a single place where all dependencies are exported as properties of an Object. This way, one's own code does not have to import them everywhere / updates can happen in a single place.

from comments-api.

hbarcelos avatar hbarcelos commented on July 17, 2024

the idea was to have a single place where all dependencies are exported as properties of an Object.

I think this approach takes things too far. Problem here is that you'll have non-related dependencies all scattered trough a single file that might become huge.

Just imagine yourself and your team updating this big chunk of a file all at the same time. Merge conflicts will drive you crazy. The bureaucracy of adding a new dependency will quickly escalate.

I believe this strategy should be used with caution, in a more fine-grained fashion.

from comments-api.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.