New skills (14): - nestjs-best-practices: 40 priority-ranked rules (kadajett) - fastapi: Pydantic v2, async SQLAlchemy, JWT auth (jezweb) - architecture-patterns: Clean Architecture, Hexagonal, DDD (wshobson) - python-performance-optimization: Profiling and optimization (wshobson) - ai-sdk: Vercel AI SDK streaming and agent patterns (vercel) - create-agent: Modular agent architecture with OpenRouter (openrouterteam) - proactive-agent: WAL Protocol, compaction recovery, self-improvement (halthelobster) - brand-guidelines: Brand identity enforcement (anthropics) - ui-animation: Motion design with accessibility (mblode) - marketing-ideas: 139 ideas across 14 categories (coreyhaines31) - pricing-strategy: SaaS pricing and tier design (coreyhaines31) - programmatic-seo: SEO at scale with playbooks (coreyhaines31) - competitor-alternatives: Comparison page architecture (coreyhaines31) - referral-program: Referral and affiliate programs (coreyhaines31) README reorganized by domain: Code Quality, Frontend, Backend, Auth, AI/Agent Building, Marketing, Design, Meta. Mosaic Stack is not limited to coding — the Orchestrator serves coding, business, design, marketing, writing, logistics, and analysis. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
105 lines
2.9 KiB
Markdown
105 lines
2.9 KiB
Markdown
---
|
|
title: Avoid Service Locator Anti-Pattern
|
|
impact: HIGH
|
|
impactDescription: Hides dependencies and breaks testability
|
|
tags: 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):**
|
|
|
|
```typescript
|
|
// 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):**
|
|
|
|
```typescript
|
|
// 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](https://docs.nestjs.com/fundamentals/module-ref)
|