Skip to main content 首页 创作者 inugamidev ultrathink-oss dependency-injection
dependency-injection Dependency injection patterns — IoC containers, service registration, lifetime scoping, and testing.
跳到安装 Skills Marketplace 发现并探索由社区构建的 Agent Skills
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/InugamiDev/ultrathink-oss --skill dependency-injection命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
下载 Zip 下载中... Unified design foundations — design system architecture, tokens, component specs, visual principles, creative vision, figma integration, plus brand design system loader (66 real brands via DESIGN.md). Absorbs design, design-system, design-systems, design-principles, design-router, creative-vision, figma, design-md.
name dependency-injection description Dependency injection patterns — IoC containers, service registration, lifetime scoping, and testing. layer utility category architecture triggers ["dependency injection","IoC","inversion of control","DI container","service provider"] inputs ["DI architecture decisions","Service registration and lifetime scoping","IoC container selection and configuration","Testing with dependency injection"] outputs ["DI container setup and configuration","Service registration patterns","Lifetime management strategies","Testable architectures with DI"] linksTo ["nestjs","typescript-patterns","testing-patterns"] linkedFrom [] preferredNextSkills ["nestjs","typescript-patterns","testing-patterns"] fallbackSkills [] riskLevel low memoryReadPolicy selective memoryWritePolicy none sideEffects []
Dependency Injection Patterns
Purpose
Provide expert guidance on dependency injection (DI) and inversion of control (IoC) patterns including container configuration, service lifetimes, composition roots, and how DI enables testability. Covers both framework-based DI (NestJS, Angular) and standalone containers (tsyringe, inversify, awilix).
Core Concepts
Dependency Injection inverts the control of dependency creation. Instead of a class creating its own dependencies, they are provided (injected) from the outside.
class OrderService {
private db = new PostgresDatabase ();
private mailer = new SmtpMailer ();
async createOrder (data : CreateOrderDto ) {
const order = await this .db .insert ('orders' , data);
await this .mailer .send (data.email , 'Order confirmed' );
return order;
}
}
class OrderService {
constructor (
private readonly db : Database ,
private : ,
) {}
( ) {
order = . . ( , data);
. . (data. , );
order;
}
}
readonly
mailer
Mailer
async
createOrder
data : CreateOrderDto
const
await
this
db
insert
'orders'
await
this
mailer
send
email
'Order confirmed'
return
DI Patterns
Constructor Injection (Preferred) Dependencies provided via the constructor. Most explicit and testable:
interface Logger {
info (message : string ): void ;
error (message : string , error ?: Error ): void ;
}
interface UserRepository {
findById (id : string ): Promise <User | null >;
save (user : User ): Promise <User >;
}
class UserService {
constructor (
private readonly userRepo : UserRepository ,
private readonly logger : Logger ,
) {}
async getUser (id : string ): Promise <User > {
this .logger .info (`Fetching user ${id} ` );
const user = await this .userRepo .findById (id);
if (!user) throw new NotFoundException (`User ${id} not found` );
return user;
}
}
Factory Pattern When the dependency needs runtime parameters:
interface ConnectionFactory {
create (config : ConnectionConfig ): Connection ;
}
class DatabaseService {
constructor (private readonly connectionFactory : ConnectionFactory ) {}
connect (config : ConnectionConfig ) {
return this .connectionFactory .create (config);
}
}
Strategy Pattern with DI Inject different implementations based on context:
interface PaymentProcessor {
charge (amount : number , currency : string ): Promise <PaymentResult >;
}
class StripeProcessor implements PaymentProcessor {
async charge (amount : number , currency : string ) { }
}
class PayPalProcessor implements PaymentProcessor {
async charge (amount : number , currency : string ) { }
}
class PaymentService {
constructor (
private readonly processors : Map <string , PaymentProcessor >,
) {}
async processPayment (method : string , amount : number , currency : string ) {
const processor = this .processors .get (method);
if (!processor) throw new Error (`Unknown payment method: ${method} ` );
return processor.charge (amount, currency);
}
}
Service Lifetimes Lifetime Description Use Case Singleton One instance for the entire app Stateless services, config, loggers Transient New instance on every injection Stateful services, request-specific logic Scoped One instance per scope (e.g., HTTP request) Database connections, user context
import { container, singleton, injectable } from 'tsyringe' ;
@singleton ()
class ConfigService {
get (key : string ): string { }
}
@injectable ()
class RequestHandler {
constructor (private config : ConfigService ) {}
}
const requestContainer = container.createChildContainer ();
requestContainer.register ('RequestContext' , { useValue : { userId : '123' } });
IoC Containers
tsyringe (Lightweight, TypeScript-native)
import 'reflect-metadata' ;
import { container } from 'tsyringe' ;
container.register <Database >('Database' , { useClass : PostgresDatabase });
container.register <Mailer >('Mailer' , { useClass : SmtpMailer });
container.register <Logger >('Logger' , { useClass : PinoLogger });
const orderService = container.resolve (OrderService );
import { injectable, inject } from 'tsyringe' ;
@injectable ()
class OrderService {
constructor (
@inject ('Database' ) private readonly db : Database ,
@inject ('Mailer' ) private readonly mailer : Mailer ,
) {}
}
awilix (No decorators, function-oriented)
import { createContainer, asClass, asFunction, Lifetime } from 'awilix' ;
const container = createContainer ();
container.register ({
userRepository : asClass (PostgresUserRepository ).singleton (),
orderRepository : asClass (PostgresOrderRepository ).singleton (),
logger : asClass (PinoLogger ).singleton (),
userService : asClass (UserService ).scoped (),
orderService : asClass (OrderService ).scoped (),
dbConnection : asFunction (({ config } ) =>
createPool (config.databaseUrl )
).singleton (),
});
app.use ((req, _res, next ) => {
req.scope = container.createScope ();
req.scope .register ({ requestId : asValue (crypto.randomUUID ()) });
next ();
});
NestJS Built-in DI
@Module ({
providers : [
UserService ,
{ provide : 'MAILER' , useClass : SmtpMailer },
{
provide : 'DB_POOL' ,
useFactory : (config : ConfigService ) => createPool (config.get ('DATABASE_URL' )),
inject : [ConfigService ],
},
{ provide : 'APP_VERSION' , useValue : '1.0.0' },
],
})
export class UsersModule {}
Composition Root The composition root is the single place where the entire dependency graph is wired. Keep it at the app entry point:
import { container } from './container' ;
async function bootstrap ( ) {
container.register <Database >('Database' , { useClass : PostgresDatabase });
container.register <Cache >('Cache' , { useClass : RedisCache });
container.register <Mailer >('Mailer' , { useClass : ResendMailer });
container.register <Logger >('Logger' , { useClass : PinoLogger });
const app = container.resolve (App );
await app.start ();
}
bootstrap ();
Rules for the composition root:
It is the ONLY place that references concrete implementations
All other code depends on abstractions (interfaces/types)
Easy to swap implementations for testing or different environments
Testing with DI Unit tests — inject mocks directly:
describe ('OrderService' , () => {
let service : OrderService ;
let mockDb : jest.Mocked <Database >;
let mockMailer : jest.Mocked <Mailer >;
beforeEach (() => {
mockDb = {
insert : jest.fn (),
findById : jest.fn (),
} as any ;
mockMailer = {
send : jest.fn ().mockResolvedValue (undefined ),
} as any ;
service = new OrderService (mockDb, mockMailer);
});
it ('creates order and sends confirmation email' , async () => {
mockDb.insert .mockResolvedValue ({ id : '1' , status : 'created' });
await service.createOrder ({
email : 'user@test.com' ,
items : [{ productId : 'p1' , qty : 2 }],
});
expect (mockDb.insert ).toHaveBeenCalledWith ('orders' , expect.any (Object ));
expect (mockMailer.send ).toHaveBeenCalledWith ('user@test.com' , 'Order confirmed' );
});
});
Integration tests — override specific registrations:
describe ('OrderService (integration)' , () => {
let testContainer : typeof container;
beforeEach (() => {
testContainer = container.createChildContainer ();
testContainer.register <Mailer >('Mailer' , { useClass : NoopMailer });
});
it ('persists order to database' , async () => {
const service = testContainer.resolve (OrderService );
const order = await service.createOrder ({ });
expect (order.id ).toBeDefined ();
});
});
Best Practices
Depend on abstractions — Inject interfaces, not concrete classes.
Constructor injection only — Avoid property injection and service locator pattern.
Single composition root — Wire everything in one place at app startup.
Prefer singleton for stateless services — Transient/scoped only when state is per-request.
No new in business logic — If a class creates its own dependencies, it cannot be tested in isolation.
Avoid circular dependencies — Restructure into a shared module or use events.
Keep containers out of business logic — Only the composition root touches the container.
Use factory providers for runtime config — When a dependency needs config values.
Scope database connections per request — Prevent connection leaks and enable per-request transactions.
Test without the container — Unit tests should inject mocks via constructor, no container needed.
Common Pitfalls Pitfall Problem Fix Service locator anti-pattern container.resolve() called inside business logicInject via constructor; only resolve at the composition root Missing reflect-metadata Decorator-based DI fails silently Import reflect-metadata at app entry point Circular dependency Container throws at resolution time Break the cycle with events, mediator, or forwardRef() Overusing transient scope High memory usage, GC pressure Default to singleton; use transient only when stateful Injecting concrete classes Tight coupling, hard to mock Define interfaces and inject those Container in unit tests Tests become slow integration tests Inject mocks directly via constructor