Search code examples
javascripttypescriptdependency-injectionsolid-principlesdependency-inversion

Dependency Inversion Principle: is a low level module allowed to have a hidden reference to another low level module?


Consider the following code from Web Dev Simplified's video on the Dependency Inversion Principe: https://www.youtube.com/watch?v=9oHY5TllWaU

class Store {
  constructor(paymentProcessor) {
    this.paymentProcessor = paymentProcessor;
  }

  purchase(quantity) {
    this.paymentProcessor.pay(200 * quantity);
  }

}

class StripePaymentProcessor {
  constructor(user) {
    this.user = user;
    this.stripe = new Stripe(user);
  }
  pay(amountInDollars) {
    this.stripe.makePayment(amountInDollars * 100);
  }
}

class Stripe {
  constructor(user) {
    this.user = user;
  }

  makePayment(amountInCents) {
    console.log(
      `${this.user} made a payment of $${amountInCents / 100} with Stripe`
    );
  }
}

class PayPalPaymentProcessor {
  constructor(user) {
    this.user = user;
    this.paypal = new PayPal();
  }
  pay(amountInDollars) {
    this.paypal.makePayment(this.user, amountInDollars);
  }
}

class PayPal {
  makePayment(user, amountInDollars) {
    console.log(`${user} made a payment of $${amountInDollars} with PayPal`);
  }
}

// const store = new Store(new StripePaymentProcessor('John'));
const store = new Store(new PayPalPaymentProcessor('John'));
store.purchase(2);

This should be an example of successful implementation of the dependency inversion principle, as we are loosely coupling the high level module Store & the low level modules Stripe & Paypal.

What bothers me, however, is that we are creating new instances of Stripe & Paypal in both StripePaymentProcessor & PayPalPaymentProcessor.

Since we are instantiating another class inside those classes, without injecting it via the parameters, are we not creating a hidden dependency & therefore violating the D in SOLID ? Are we not just moving the dependency to another class?

If this is no violation, are we then allowed to instantiate a different low level module inside a low level module?

Would including each payment processor in Typescript as a parameter to Store which uses a PaymentProcessor interface solve the dependency issue by applying an abstraction?

This has been making me mad & I'd appreciate some clarification. Thank you.


Solution

  • Look from this perspective, Store is your domain code and it needs to execute payment code. Now most often, for payment processing, we depend upon external code (3rd party libraries). We can change our code but we cannot change 3rd party code and that is where we create boundaries. That is why you created Dependency inversion there.

    The StripePaymentProcessor and PaypalPaymentProcessor are your anti-corruption/gateway/adapter classes and they have only 1 responsibility to adapt the external code to fulfill your domain requirements. That is why they are expected to have dependency over the thing they are adapting.

    We created DI to contain the ripple effect in case if external code's API changes and in this case it is contained within both PaymentProcessor classes. Even if the API of any external code changes we only have to update the respective PaymentProcessor to adapt to the new change.

    You can check an example of DI here.