Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
40 prioritized NestJS best practices across architecture, DI, security, performance, testing, and microservices.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
rules/di-interface-segregation.md
1---2title: Apply Interface Segregation Principle3impact: HIGH4impactDescription: Reduces coupling and improves testability by 30-50%5tags: dependency-injection, interfaces, solid, isp6---78## Apply Interface Segregation Principle910Clients should not be forced to depend on interfaces they don't use. In NestJS, this means keeping interfaces small and focused on specific capabilities rather than creating "fat" interfaces that bundle unrelated methods. When a service only needs to send emails, it shouldn't depend on an interface that also includes SMS, push notifications, and logging. Split large interfaces into role-based ones.1112**Incorrect (fat interface forcing unused dependencies):**1314```typescript15// Fat interface - forces all consumers to depend on everything16interface NotificationService {17sendEmail(to: string, subject: string, body: string): Promise<void>;18sendSms(phone: string, message: string): Promise<void>;19sendPush(userId: string, notification: PushPayload): Promise<void>;20sendSlack(channel: string, message: string): Promise<void>;21logNotification(type: string, payload: any): Promise<void>;22getDeliveryStatus(id: string): Promise<DeliveryStatus>;23retryFailed(id: string): Promise<void>;24scheduleNotification(dto: ScheduleDto): Promise<string>;25}2627// Consumer only needs email, but must mock everything for tests28@Injectable()29export class OrdersService {30constructor(31private notifications: NotificationService, // Depends on 8 methods, uses 132) {}3334async confirmOrder(order: Order): Promise<void> {35await this.notifications.sendEmail(36order.customer.email,37'Order Confirmed',38`Your order ${order.id} has been confirmed.`,39);40}41}4243// Testing is painful - must mock unused methods44const mockNotificationService = {45sendEmail: jest.fn(),46sendSms: jest.fn(), // Never used, but required47sendPush: jest.fn(), // Never used, but required48sendSlack: jest.fn(), // Never used, but required49logNotification: jest.fn(), // Never used, but required50getDeliveryStatus: jest.fn(), // Never used, but required51retryFailed: jest.fn(), // Never used, but required52scheduleNotification: jest.fn(), // Never used, but required53};54```5556**Correct (segregated interfaces by capability):**5758```typescript59// Segregated interfaces - each focused on one capability60interface EmailSender {61sendEmail(to: string, subject: string, body: string): Promise<void>;62}6364interface SmsSender {65sendSms(phone: string, message: string): Promise<void>;66}6768interface PushSender {69sendPush(userId: string, notification: PushPayload): Promise<void>;70}7172interface NotificationLogger {73logNotification(type: string, payload: any): Promise<void>;74}7576interface NotificationScheduler {77scheduleNotification(dto: ScheduleDto): Promise<string>;78}7980// Implementation can implement multiple interfaces81@Injectable()82export class NotificationService implements EmailSender, SmsSender, PushSender {83async sendEmail(to: string, subject: string, body: string): Promise<void> {84// Email implementation85}8687async sendSms(phone: string, message: string): Promise<void> {88// SMS implementation89}9091async sendPush(userId: string, notification: PushPayload): Promise<void> {92// Push implementation93}94}9596// Or separate implementations97@Injectable()98export class SendGridEmailService implements EmailSender {99async sendEmail(to: string, subject: string, body: string): Promise<void> {100// SendGrid-specific implementation101}102}103104// Consumer depends only on what it needs105@Injectable()106export class OrdersService {107constructor(108@Inject(EMAIL_SENDER) private emailSender: EmailSender, // Minimal dependency109) {}110111async confirmOrder(order: Order): Promise<void> {112await this.emailSender.sendEmail(113order.customer.email,114'Order Confirmed',115`Your order ${order.id} has been confirmed.`,116);117}118}119120// Testing is simple - only mock what's used121const mockEmailSender: EmailSender = {122sendEmail: jest.fn(),123};124125// Module registration with tokens126export const EMAIL_SENDER = Symbol('EMAIL_SENDER');127export const SMS_SENDER = Symbol('SMS_SENDER');128129@Module({130providers: [131{ provide: EMAIL_SENDER, useClass: SendGridEmailService },132{ provide: SMS_SENDER, useClass: TwilioSmsService },133],134exports: [EMAIL_SENDER, SMS_SENDER],135})136export class NotificationModule {}137```138139**Combining interfaces when needed:**140141```typescript142// Sometimes a consumer legitimately needs multiple capabilities143interface EmailAndSmsSender extends EmailSender, SmsSender {}144145// Or use intersection types146type MultiChannelSender = EmailSender & SmsSender & PushSender;147148// Consumer that genuinely needs multiple channels149@Injectable()150export class AlertService {151constructor(152@Inject(MULTI_CHANNEL_SENDER)153private sender: EmailSender & SmsSender,154) {}155156async sendCriticalAlert(user: User, message: string): Promise<void> {157await Promise.all([158this.sender.sendEmail(user.email, 'Critical Alert', message),159this.sender.sendSms(user.phone, message),160]);161}162}163```164165Reference: [Interface Segregation Principle](https://en.wikipedia.org/wiki/Interface_segregation_principle)166