NestJS에서 SOLID 원칙을 적용하여 코드를 최적화하는 방법
2024-11-09 22:21:51NestJS와 SOLID: 코드 품질을 향상시키는 방법
NestJS는 Node.js 환경에서 확장 가능하고 모듈화된 백엔드 애플리케이션을 구축하기 위한 진보적인 프레임워크입니다. 그러나 확장성을 뛰어넘어 유지보수성과 코드 가독성을 높이려면 SOLID 원칙을 이해하고 적용하는 것이 중요합니다. 이 글에서는 각 SOLID 원칙을 검토하고, NestJS에서 이를 어떻게 구현할 수 있는지 예시를 통해 알아보겠습니다.
SRP: 단일 책임 원칙
단일 책임 원칙(SRP)은 클래스가 변경을 일으킬 단 한 가지 이유만 가져야 한다는 원칙입니다. NestJS에서는 이 원칙을 서로 다른 서비스를 담당하는 개별 클래스로 구현해 볼 수 있습니다.
예제: 사용자 및 인증 서비스
사용자 정보 관리와 인증 기능이 결합된 서비스가 있는 경우, 이를 분리하여 주어진 책임을 명확히 할 수 있습니다.
// src/user/user.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class UserService {
async createUser(data) { /* 사용자 생성 로직 */ }
async getUserById(id) { /* 사용자 아이디로 데이터 조회 로직 */ }
}
// src/auth/auth.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class AuthService {
async login(credentials) { /* 로그인 로직 */ }
async validateUser(token) { /* 사용자 검증 로직 */ }
}
이렇게 분리하면 코드의 유지보수가 쉬워지며, 변경 사항이 두 개의 책임을 가진 서비스가 아닌 해당 기능만 포함한 서비스에만 영향을 미칩니다.
OCP: 개방-폐쇄 원칙
개방-폐쇄 원칙(OCP)은 확장을 허용하지만 수정은 금지되는 객체지향 설계의 원칙입니다. 이를 통해 코드를 안정적으로 유지하는 동시에 신규 기능을 추가할 수 있습니다.
예제: 확장 가능한 알림 시스템
기존 코드를 변경하지 않고 이메일과 SMS 등 다양한 알림 채널을 지원하려면 다음과 같이 인터페이스를 설계할 수 있습니다.
// src/notification/interfaces/notification.interface.ts
export interface NotificationService {
sendNotification(message: string): Promise<void>;
}
// src/notification/services/email-notification.service.ts
import { Injectable } from '@nestjs/common';
import { NotificationService } from '../interfaces/notification.interface';
@Injectable()
export class EmailNotificationService implements NotificationService {
async sendNotification(message: string): Promise<void> {
console.log('이메일 알림 전송:', message);
}
}
// src/notification/services/sms-notification.service.ts
import { Injectable } from '@nestjs/common';
import { NotificationService } from '../interfaces/notification.interface';
@Injectable()
export class SmsNotificationService implements NotificationService {
async sendNotification(message: string): Promise<void> {
console.log('SMS 알림 전송:', message);
}
}
새로운 알림 채널을 추가하려면 간단히 NotificationService 인터페이스를 구현하기만 하면 됩니다.
LSP: 리스코프 치환 원칙
리스코프 치환 원칙(LSP)은 프로그램의 기능을 변경하지 않으면서 유도 클래스가 기초 클래스를 대체할 수 있어야 함을 나타냅니다. NestJS의 종속성 주입을 통해 이를 쉽게 구현할 수 있습니다.
예제: 알림 서비스의 치환 가능성
알림 서비스를 사용하는 Controller는 어떤 알림 방식이든 일관되게 사용할 수 있습니다.
// src/notification/notification.controller.ts
import { Controller, Inject } from '@nestjs/common';
import { NotificationService } from './interfaces/notification.interface';
@Controller('notification')
export class NotificationController {
constructor(
@Inject('NotificationService') private readonly notificationService: NotificationService,
) {}
sendMessage(message: string) {
this.notificationService.sendNotification(message);
}
}
어떤 알림 서비스를 주입하든 NotificationController는 문제 없이 기능을 수행합니다.
ISP: 인터페이스 분리 원칙
인터페이스 분리 원칙(ISP)은 구현하지 않을 인터페이스의 의존은 피해야 한다고 강조합니다. 이는 부담 없이 필요한 기능만을 가진 작은 인터페이스를 만드는 방식으로 대응할 수 있습니다.
예제: 인터페이스 분리 전략
// src/repository/interfaces/read.interface.ts
export interface Read<T> {
findAll(): Promise<T[]>;
findOne(id: string): Promise<T>;
}
// src/repository/interfaces/write.interface.ts
export interface Write<T> {
create(item: T): Promise<T>;
update(id: string, item: T): Promise<T>;
delete(id: string): Promise<void>;
}
// src/repository/interfaces/repository.interface.ts
export interface Repository<T> extends Read<T>, Write<T> {}
서비스는 필요로 하는 기능만 구현하면 되므로 불필요한 코드 작성과 유지보수 비용을 줄일 수 있습니다.
DIP: 의존성 역전 원칙
의존성 역전 원칙(DIP)은 고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 모두 추상화에 의존해야 한다고 설명합니다. 이를 구현하면 높은 모듈 간 결합도를 낮출 수 있습니다.
예제: 변경을 최소화하는 구조
// src/notification/notification.module.ts
import { Module } from '@nestjs/common';
import { NotificationController } from './notification.controller';
import { EmailNotificationService } from './services/email-notification.service';
@Module({
controllers: [NotificationController],
providers: [{ provide: 'NotificationService', useClass: EmailNotificationService }],
})
export class NotificationModule {}
다양한 알림 서비스로의 전환이 가능하면서도 공통 인터페이스를 사용하는 구조를 유지할 수 있습니다.
결론 및 추가 자료
NestJS와 같은 프레임워크에서 SOLID 원칙을 구현하면 구조적이고 유지보수 가능한 코드를 작성할 수 있습니다. 이는 코드의 신뢰성을 높이고 변화에 빠르게 적응할 수 있게 합니다. SOLID를 통해 더 향상된 프로그래밍을 꿈꾸는 개발자들은 아래 자료를 참고하면 많은 도움이 될 것입니다.
초급 개발자부터 고급 개발자에게 모두 유용한 SOLID 원칙은 큰 프로젝트에서도 특히 중요한 역할을 하며, 이러한 방법론에 대한 심도 있는 이해는 코드 품질과 프로젝트의 성과에 크게 기여할 것입니다.