The Dependency Inversion Principle (DIP) is a key SOLID principle that helps us write flexible, maintainable code by decoupling high-level logic from low-level details. Instead of hardcoding dependencies, we rely on abstractions. Today, we’ll explore DIP with a practical example: a UserService
that works with MongoDB, MySQL, and PostgreSQL, all in TypeScript.
The Problem: Tight Coupling
Imagine a UserService
that directly uses a MySQL database. If you later need to switch to MongoDB, you’d rewrite the service. That’s rigid and messy. DIP fixes this by making both the service and the database depend on an abstraction.
Step 1: Define the Abstraction
We start with an interface that defines what our database should do—here, saving a user.
typescriptinterface UserRepository {
saveUser(user: { id: string; name: string }): Promise<void>;
}
This is our contract. The UserService
will depend on this, not a specific database.
Step 2: Implement Concrete Repositories
Next, we create implementations for MongoDB, MySQL, and PostgreSQL, each adhering to UserRepository
.
MongoDB Repository
typescriptimport { MongoClient } from "mongodb";
class MongoUserRepository implements UserRepository {
private client: MongoClient;
constructor() {
this.client = new MongoClient("mongodb://localhost:27017");
}
async saveUser(user: { id: string; name: string }): Promise<void> {
const db = this.client.db("mydb");
await db.collection("users").insertOne(user);
console.log(`Saved ${user.name} to MongoDB`);
}
}
MySQL Repository
typescriptimport mysql from "mysql2/promise";
class MySQLUserRepository implements UserRepository {
private connection: mysql.Connection;
constructor() {
this.connection = await mysql.createConnection({
host: "localhost",
user: "root",
database: "mydb",
});
}
async saveUser(user: { id: string; name: string }): Promise<void> {
await this.connection.execute(
"INSERT INTO users (id, name) VALUES (?, ?)",
[user.id, user.name]
);
console.log(`Saved ${user.name} to MySQL`);
}
}
PostgreSQL Repository
typescriptimport { Pool } from "pg";
class PostgresUserRepository implements UserRepository {
private pool: Pool;
constructor() {
this.pool = new Pool({
user: "postgres",
host: "localhost",
database: "mydb",
password: "password",
});
}
async saveUser(user: { id: string; name: string }): Promise<void> {
await this.pool.query(
"INSERT INTO users (id, name) VALUES ($1, $2)",
[user.id, user.name]
);
console.log(`Saved ${user.name} to PostgreSQL`);
}
}
Each class implements UserRepository
, but the details (SQL queries, MongoDB collections) are hidden behind the interface.
Step 3: The High-Level Service
Now, the UserService
depends only on the UserRepository
abstraction, not the concrete databases.
typescriptclass UserService {
private repository: UserRepository;
constructor(repository: UserRepository) {
this.repository = repository; // Dependency injected
}
async addUser(user: { id: string; name: string }) {
await this.repository.saveUser(user);
}
}
Step 4: Putting It Together
We can now swap databases without touching UserService
.
typescriptasync function main() {
const user = { id: "1", name: "Alice" };
// Use MongoDB
const mongoRepo = new MongoUserRepository();
const mongoService = new UserService(mongoRepo);
await mongoService.addUser(user);
// Switch to MySQL
const mysqlRepo = new MySQLUserRepository();
const mysqlService = new UserService(mysqlRepo);
await mysqlService.addUser(user);
// Switch to PostgreSQL
const postgresRepo = new PostgresUserRepository();
const postgresService = new UserService(postgresRepo);
await postgresService.addUser(user);
}
main().catch(console.error);
Why DIP Shines Here
- Flexibility: Need a new database? Just write a new repository class.
-
Testability: Mock
UserRepository
for unit tests. -
Clean Code:
UserService
doesn’t care about database details—it’s blissfully ignorant.
Wrapping Up
DIP turns dependencies upside down: instead of UserService
dictating a specific database, both rely on the UserRepository
interface. This small shift makes your TypeScript apps more modular and adaptable. Try it out next time you’re wiring up a service—your future self will thank you!
Album of the day: