// SOLID & Principles · 05
Dependency Inversion
High-level policy owns the interface; details implement it.
How it fits together
- InvoiceService uses / calls DbInvoiceStore (direct call)
- DbInvoiceStore uses / calls InvoiceDb (ORM query)
- InvoiceService uses / calls InvoiceStore (via abstraction)
- DbInvoiceStore implements InvoiceStore
- DbInvoiceStore uses / calls InvoiceDb (ORM query)
Spot it
- Business logic opens its own database connection
- Can't unit test without a real database
- Swapping a vendor means editing core logic
It costs
Extra indirection; abstracting stable, single-implementation details only adds noise.
In code
public interface IInvoiceStore{ Task SaveAsync(Invoice invoice, CancellationToken ct); Task<Invoice?> FindByIdAsync(Guid id, CancellationToken ct);}public sealed class InvoiceService(IInvoiceStore store){ public async Task IssueAsync(Invoice invoice, CancellationToken ct) { invoice.MarkIssued(); await store.SaveAsync(invoice, ct); } public Task<Invoice?> FindAsync(Guid id, CancellationToken ct) => store.FindByIdAsync(id, ct);}public sealed class DbInvoiceStore(InvoiceDb db) : IInvoiceStore{ public Task SaveAsync(Invoice invoice, CancellationToken ct) => db.UpsertInvoiceAsync(invoice, ct); public Task<Invoice?> FindByIdAsync(Guid id, CancellationToken ct) => db.FindInvoiceAsync(id, ct);}// Program.csbuilder.Services.AddScoped<IInvoiceStore, DbInvoiceStore>();Go deeperThe full explanation and what to ask for · 1 min readExpandCollapse
Two rules: high-level modules should not depend on low-level modules, both should depend on abstractions; and abstractions should not depend on details. The inversion is in ownership: the interface lives with the policy that uses it, not with the database code.
Dependency inversion is not dependency injection. DI is a technique for supplying dependencies; you can inject a concrete DbInvoiceStore and still have the dependency pointing the wrong way.
Invert at boundaries that are volatile or slow to test: I/O, external services, time. Depending directly on stable framework and language types is fine.
Ask for it
- abstraction
- dependency injection
- composition root
- ports and adapters
- inversion of control
Invert the dependency in InvoiceService: define an invoice store interface in the Application project, implement it with an ORM in Infrastructure, and wire it in the composition root through the DI container. Application must no longer reference Infrastructure.