skip to content
~/amin
← cd ~/courses/solid

lesson 5/5 · 0:58

Dependency Inversion Principle

Switch email providers in one line and test sign-up without sending real emails, by depending on an interface you own.

“High-level modules should not depend on low-level modules. Both should depend on abstractions.”

— Robert C. Martin

The problem in code

Your sign-up code builds its own Gmail sender. Marketing switches to SendGrid, and you rewrite Registration. Every sign-up test sends a real email.

class Registration {
  private mailer = new GmailSender(process.env.GMAIL_KEY!);

  register(user: User) {
    db.save(user);
    this.mailer.send(user.email, 'Welcome!');
  }
}

The fix

Registration asks for a Mailer, an interface it owns. Gmail and SendGrid implement it and are plugged in from outside.

interface Mailer {
  send(to: string, subject: string): Promise<void>;
}

class Registration {
  constructor(private mailer: Mailer) {}
  async register(user: User) {
    db.save(user);
    await this.mailer.send(user.email, 'Welcome!');
  }
}

// Startup: switching providers is one line.
const registration = new Registration(new SendGridMailer(key));

// Tests: no real emails.
const sent: string[] = [];
const fake: Mailer = { send: async (to) => { sent.push(to); } };
new Registration(fake);

Takeaway

The important code (sign-up rules) defines the interface; the details (Gmail, SendGrid) implement it. That’s the “inversion”: details depend on your business code, not the other way around.

That’s all five letters of SOLID.