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.”
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.