2.9 KiB
2.9 KiB
title, impact, impactDescription, tags
| title | impact | impactDescription | tags |
|---|---|---|---|
| Avoid Service Locator Anti-Pattern | HIGH | Hides dependencies and breaks testability | dependency-injection, anti-patterns, testing |
Avoid Service Locator Anti-Pattern
Avoid using ModuleRef.get() or global containers to resolve dependencies at runtime. This hides dependencies, makes code harder to test, and breaks the benefits of dependency injection. Use constructor injection instead.
Incorrect (service locator anti-pattern):
// Use ModuleRef to get dependencies dynamically
@Injectable()
export class OrdersService {
constructor(private moduleRef: ModuleRef) {}
async createOrder(dto: CreateOrderDto): Promise<Order> {
// Dependencies are hidden - not visible in constructor
const usersService = this.moduleRef.get(UsersService);
const inventoryService = this.moduleRef.get(InventoryService);
const paymentService = this.moduleRef.get(PaymentService);
const user = await usersService.findOne(dto.userId);
// ... rest of logic
}
}
// Global singleton container
class ServiceContainer {
private static instance: ServiceContainer;
private services = new Map<string, any>();
static getInstance(): ServiceContainer {
if (!this.instance) {
this.instance = new ServiceContainer();
}
return this.instance;
}
get<T>(key: string): T {
return this.services.get(key);
}
}
Correct (constructor injection with explicit dependencies):
// Use constructor injection - dependencies are explicit
@Injectable()
export class OrdersService {
constructor(
private usersService: UsersService,
private inventoryService: InventoryService,
private paymentService: PaymentService,
) {}
async createOrder(dto: CreateOrderDto): Promise<Order> {
const user = await this.usersService.findOne(dto.userId);
const inventory = await this.inventoryService.check(dto.items);
// Dependencies are clear and testable
}
}
// Easy to test with mocks
describe('OrdersService', () => {
let service: OrdersService;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
OrdersService,
{ provide: UsersService, useValue: mockUsersService },
{ provide: InventoryService, useValue: mockInventoryService },
{ provide: PaymentService, useValue: mockPaymentService },
],
}).compile();
service = module.get(OrdersService);
});
});
// VALID: Factory pattern for dynamic instantiation
@Injectable()
export class HandlerFactory {
constructor(private moduleRef: ModuleRef) {}
getHandler(type: string): Handler {
switch (type) {
case 'email':
return this.moduleRef.get(EmailHandler);
case 'sms':
return this.moduleRef.get(SmsHandler);
default:
return this.moduleRef.get(DefaultHandler);
}
}
}
Reference: NestJS Module Reference