의존성 역전 원칙(DIP) 고수

클린 아키텍처 구조 중 중요한 원칙 중 하나는의존성 역전 원칙(DIP) 입니다. "상위 모듈은 하위 모듈에 의존해서는 안 된다. 둘 다 추상화에 의존해야 한다."는 내용을 담고 있습니다.

Before: 직접적인 의존성

// 잘못된 방식: 비즈니스 로직이 Prisma라는 '구현체'에 직접 의존한다.
class RoutineService {
  private prisma = new PrismaClient();

  async getRoutine(id: number) {
    // 만약 Prisma를 다른 ORM으로 바꾸면 이 코드는 전부 수정되어야 한다.
    return this.prisma.routine.findUnique({ where: { id } });
  }
}

After: 추상화에 대한 의존

IRoutinesRepository라는 인터페이스(추상화)를 domains 계층에 정의했습니다. 이 인터페이스는 "루틴을 어떻게 가져올 것인가(How)"가 아닌, "루틴을 가져올 수 있다(What)"는 약속만을 담고 있습니다.

// domains/repositories/IRoutinesRepository.ts
export interface IRoutinesRepository {
  findById(routineId: number): Promise<Routine | null>;
  // ...
}

// application/usecases/GetRoutineByIdUseCase.ts
export class GetRoutineByIdUseCase {
  // 생성자를 통해 '설계도(인터페이스)'를 주입받는다.
  // 실제 Prisma 구현체가 들어오는지, 다른 것이 들어오는지 전혀 신경 쓰지 않는다.
  constructor(private readonly routinesRepository: IRoutinesRepository) {}

  async execute(request: GetRoutineByIdRequestDto): Promise<ReadRoutineResponseDto> {
    // 약속된 기능만을 호출한다.
    const routine = await this.routinesRepository.findById(request.routineId);
    // ...
  }
}

 

비즈니스 로직(UseCase)은 이제 Prisma의 존재를 알지 못합니다. 오직 IRoutinesRepository라는 '약속'에만 의존합니다. 따라서, 데이터베이스 기술이 바뀌어도 비즈니스 로직은 수정할 필요가 없어졌으며 Repository를 가짜 객체로 대체하여 손쉽게 단위 테스트를 작성할 수 있습니다. 이것이 DIP가 주는 유연성과 테스트 용이성입니다.

백엔드 기능 구현

오늘 하루  다음과 같은 성과를 이뤄냈습니다.

  • 확장 가능한 API 설계 : RESTful 표준을 준수하고, 일관된 응답 형식과 명확한 HTTP 상태 코드를 적용하여 총 10개의 API 엔드포인트를 구현했습니다.
  • 테스트 완료 : API 통합 테스트 결과, 테스트를 모두 통과 했습니다.

정리

IRoutinesRepository라는 추상적인 인터페이스에서 시작하여 Next.js의 NextResponse로 데이터가 반환되기까지의 과정을 거쳤습니다. 제가 맡은 "메인 대시보드 및 루틴 수행" 기능의 백엔드는 모두 구현이 완료되었으며. GET /api/routines?userId={id} POST /api/routines/{id}/complete API를 이용하여 프론트엔드단 개발을 진행할 예정입니다. 완성된 API를 호출하여 메인 페이지에 기능을 구현하고 사용자가 실제로 루틴을 완료하는 인터랙션을 구현하는 데 집중할 계획입니다.

728x90