--- title: Apply Interface Segregation Principle impact: HIGH impactDescription: Reduces coupling and improves testability by 30-50% tags: dependency-injection, interfaces, solid, isp --- ## Apply Interface Segregation Principle Clients 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. **Incorrect (fat interface forcing unused dependencies):** ```typescript // Fat interface - forces all consumers to depend on everything interface NotificationService { sendEmail(to: string, subject: string, body: string): Promise; sendSms(phone: string, message: string): Promise; sendPush(userId: string, notification: PushPayload): Promise; sendSlack(channel: string, message: string): Promise; logNotification(type: string, payload: any): Promise; getDeliveryStatus(id: string): Promise; retryFailed(id: string): Promise; scheduleNotification(dto: ScheduleDto): Promise; } // Consumer only needs email, but must mock everything for tests @Injectable() export class OrdersService { constructor( private notifications: NotificationService, // Depends on 8 methods, uses 1 ) {} async confirmOrder(order: Order): Promise { await this.notifications.sendEmail( order.customer.email, 'Order Confirmed', `Your order ${order.id} has been confirmed.`, ); } } // Testing is painful - must mock unused methods const mockNotificationService = { sendEmail: jest.fn(), sendSms: jest.fn(), // Never used, but required sendPush: jest.fn(), // Never used, but required sendSlack: jest.fn(), // Never used, but required logNotification: jest.fn(), // Never used, but required getDeliveryStatus: jest.fn(), // Never used, but required retryFailed: jest.fn(), // Never used, but required scheduleNotification: jest.fn(), // Never used, but required }; ``` **Correct (segregated interfaces by capability):** ```typescript // Segregated interfaces - each focused on one capability interface EmailSender { sendEmail(to: string, subject: string, body: string): Promise; } interface SmsSender { sendSms(phone: string, message: string): Promise; } interface PushSender { sendPush(userId: string, notification: PushPayload): Promise; } interface NotificationLogger { logNotification(type: string, payload: any): Promise; } interface NotificationScheduler { scheduleNotification(dto: ScheduleDto): Promise; } // Implementation can implement multiple interfaces @Injectable() export class NotificationService implements EmailSender, SmsSender, PushSender { async sendEmail(to: string, subject: string, body: string): Promise { // Email implementation } async sendSms(phone: string, message: string): Promise { // SMS implementation } async sendPush(userId: string, notification: PushPayload): Promise { // Push implementation } } // Or separate implementations @Injectable() export class SendGridEmailService implements EmailSender { async sendEmail(to: string, subject: string, body: string): Promise { // SendGrid-specific implementation } } // Consumer depends only on what it needs @Injectable() export class OrdersService { constructor( @Inject(EMAIL_SENDER) private emailSender: EmailSender, // Minimal dependency ) {} async confirmOrder(order: Order): Promise { await this.emailSender.sendEmail( order.customer.email, 'Order Confirmed', `Your order ${order.id} has been confirmed.`, ); } } // Testing is simple - only mock what's used const mockEmailSender: EmailSender = { sendEmail: jest.fn(), }; // Module registration with tokens export const EMAIL_SENDER = Symbol('EMAIL_SENDER'); export const SMS_SENDER = Symbol('SMS_SENDER'); @Module({ providers: [ { provide: EMAIL_SENDER, useClass: SendGridEmailService }, { provide: SMS_SENDER, useClass: TwilioSmsService }, ], exports: [EMAIL_SENDER, SMS_SENDER], }) export class NotificationModule {} ``` **Combining interfaces when needed:** ```typescript // Sometimes a consumer legitimately needs multiple capabilities interface EmailAndSmsSender extends EmailSender, SmsSender {} // Or use intersection types type MultiChannelSender = EmailSender & SmsSender & PushSender; // Consumer that genuinely needs multiple channels @Injectable() export class AlertService { constructor( @Inject(MULTI_CHANNEL_SENDER) private sender: EmailSender & SmsSender, ) {} async sendCriticalAlert(user: User, message: string): Promise { await Promise.all([ this.sender.sendEmail(user.email, 'Critical Alert', message), this.sender.sendSms(user.phone, message), ]); } } ``` Reference: [Interface Segregation Principle](https://en.wikipedia.org/wiki/Interface_segregation_principle)