Skip to main content

Depend on Abstractions

The Dependency Inversion Principle says: high-level code — your business logic — should not depend on low-level details like a specific database or file format. Both should depend on an abstraction: in TypeScript, an interface.

The "inversion" is the direction of the arrow. Instead of the service pointing at one concrete storage, the service points at an interface — and every storage points at that same interface from the other side.


Hard-Wired to the Details

This user service creates and uses a concrete DiskStore directly. It works — but the service is welded to one storage. A different store, a faster store, a fake store for tests: every one of them means editing the service itself.

The service welded to its storage

UserService builds its own DiskStore inside. High-level registration logic now depends on a low-level detail — the file on disk.

Prompt
Write a TypeScript program showing a Dependency Inversion violation: a UserService whose register method uses a concrete DiskStore created inside the service, printing that the user was written to a file on disk. Register one user.
Output: TypeScript file and captured output

Invert the Dependency

The fix: the service asks for a UserStorage interface in its constructor and never learns which one it got. The disk store and the memory store both implement the interface.

Now the arrow points from both sides to the abstraction — and swapping the storage is a one-line change at the place where the service is created, not inside it.

The service accepts an interface

Same registration logic, two different storages. new UserService(new DiskStore()) or new UserService(new MemoryStore()) — the service code itself never changes.

Prompt
Invert the dependency in TypeScript: define a UserStorage interface with a save method, a DiskStore and a MemoryStore class that implement it, and a UserService whose constructor accepts any UserStorage. Register one user through the disk store and one through the memory store to show the swap without changing the service.
Output: TypeScript file and captured output
The service accepts an interface

The payoff: testing without a disk

Because the service depends on the abstraction, a test can hand it a memory store and check the result instantly — no files, no cleanup. Dependency inversion is what makes code easy to unit-test.

Prompt
Show the testing payoff of Dependency Inversion in TypeScript: give MemoryStore an array recording saved users, hand it to the UserService, register two users, and verify the memory store holds both, printing the check result and the saved users like a small test.
Output: TypeScript file and captured output