| name | decoupling-with-di |
| description | Decouples modules using ports, adapters, and a DI container (tsyringe / inversify / NestJS). Use when wiring services, removing direct imports of concrete classes, or breaking circular dependencies. |
| license | MIT |
Decoupling with DI
When to use
- Wiring services and repositories
- Removing direct imports of concrete classes
- Breaking circular dependencies
- Switching DI containers
Core rules
- Domain/application declare ports (interfaces), infrastructure implements them
- Classes receive collaborators via constructor injection
- No direct imports of concrete classes across layers
- Use DI container (tsyringe, inversify, or NestJS built-in)
- Layer dependency:
infrastructure → application → domain
Reference shape (TypeScript)
Port (Interface in Application Layer)
export interface UserRepository {
findById(id: string): Promise<User | null>;
save(user: User): Promise<User>;
}
Adapter (Implementation in Infrastructure)
@injectable()
export class TypeOrmUserRepository implements UserRepository {
constructor(private ormRepo: Repository<UserOrmEntity>) {}
async findById(id: string): Promise<User | null> { }
async save(user: User): Promise<User> { }
}
Consumer (Use-Case)
@injectable()
export class CreateUserUseCase {
constructor(@inject('UserRepository') private readonly repo: UserRepository) {}
}
Examples — Do
class OrderService {
constructor(private readonly repo: OrderRepository) {}
}
Examples — Don't
import { TypeOrmOrderRepository } from '../infrastructure/typeorm-order.repository';
class OrderService {
private repo = new TypeOrmOrderRepository();
}
Checklist
See reference/di-patterns.md for full patterns.