ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 🚀 [NestJS] Saga 패턴의 시작: RabbitMQ와 함께하는 비동기 메뉴 분석 API 구현기
    프로젝트/개인프로젝트 2026. 3. 8. 12:59

    안녕하세요! 오늘은 스마트 식단 관리 에이전트 프로젝트의 핵심, 메뉴판 분석 요청 API를 개발하며 겪은 우여곡절과 기술적 고민들을 정리해 보려고 합니다. 🥗✨

    이번 작업의 핵심은 "무거운 AI 분석 작업을 어떻게 사용자 대기 시간 없이 처리할 것인가?"였습니다.


    🏗️ 1. 아키텍처 설계: 왜 Saga 패턴인가?

    사용자가 메뉴판 이미지를 업로드하면 OCR과 AI 분석이 돌아가야 하는데, 이 작업은 수 초 이상 걸릴 수 있습니다. 이를 동기(Sync) 방식으로 처리하면 유저는 화면이 멈춘 듯한 경험을 하게 되죠.

    그래서 저는 Saga 패턴의 첫 단계를 도입했습니다.

    1. NestJS: 이미지 업로드 즉시 DB에 태스크를 저장하고 taskId를 반환 (비동기 처리 시작).
    2. RabbitMQ: 분석에 필요한 정보를 메시지 큐에 담아 FastAPI 분석 엔진으로 전달.

    🛠️ 2. 트러블슈팅: 내가 만난 에러와 해결책

    ❌ Issue 1: "나 분명히 만들었는데?" (404 Not Found)

    새로운 모듈을 생성하고 포스트맨으로 쐈는데 404 Not Found가 떴습니다.

    원인: NestJS의 중앙 관리실인 AppModule에 신규 모듈인 AnalysisModule을 등록하지 않아 서버가 해당 경로를 인식하지 못했습니다.
    해결: app.module.ts의 imports 배열에 모듈을 추가하여 해결!

    ❌ Issue 2: FileTypeValidator의 배신 (400 Bad Request)

    이미지 파일만 받으려고 FileTypeValidator를 썼는데, 계속해서 검증 실패 에러가 났습니다.

    원인: 저희 서버는 메모리 효율을 위해 파일을 디스크에 바로 저장하는 diskStorage를 사용합니다. 하지만 NestJS의 기본 Validator는 메모리에 있는 file.buffer를 필요로 하기 때문에 디스크 저장 시에는 버퍼가 비어있어 검증에 실패한 것이죠.
    해결: 컨트롤러에서 file.mimetype을 직접 대조하는 수동 검증 로직으로 교체하여 해결했습니다. 💪

    // mimetype 직접 검증
        const allowedMimeTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
        if (!allowedMimeTypes.includes(file.mimetype)) {
          throw new BadRequestException('이미지 파일만 업로드 가능합니다. (jpeg, jpg, png, gif)');
        }

    💻 3. 핵심 코드 구현

    📁 분석 요청 컨트롤러 (Swagger 적용)

    파일 업로드 형식을 명시하여 스웨거 문서에서도 편리하게 테스트할 수 있도록 구성했습니다.

    @Post('analyze')
    @UseInterceptors(FileInterceptor('image'))
    @ApiConsumes('multipart/form-data')
    async analyzeMenu(@UploadedFile() file: Express.Multer.File) {
        // 1. mimetype 직접 검증
        const allowedMimeTypes = ['image/jpeg', 'image/png'];
        if (!allowedMimeTypes.includes(file.mimetype)) throw new BadRequestException('이미지만 가능!');
    
        // 2. 서비스 호출 (DB 저장 및 RabbitMQ 발행)
        return this.analysisService.createAnalysisTask(file.path);
    }
    

    📸 4. 테스트 결과 및 사진 설명

    ① 포스트맨 404 에러 상황

    • 설명: 모듈 등록 전, 서버가 경로를 찾지 못해 404 응답을 내뱉는 당황스러운 순간입니다. 이 과정을 통해 NestJS 모듈 시스템의 중요성을 다시 한번 깨달았죠.

     

    ② 분석 요청 성공 (201 Created)

    • 설명: 트러블슈팅 후, 드디어 이미지가 성공적으로 업로드된 모습입니다! 응답으로 전달된 taskId와 status: PENDING은 비동기 작업이 정상적으로 시작되었음을 알려줍니다.
    • DTO 추가 후 응답 body

     

    ③ 분석 태스크 상태 조회 (200 OK)

    • 설명: 발급받은 taskId로 상태를 조회한 결과입니다. DB에 저장된 유저의 targetCalories 정보가 분석 태스크와 결합되어 안전하게 관리되고 있습니다.

     

    ④ RabbitMQ 관리자 대시보드 (Overview)

    • 설명: 메시지 큐 전광판에 찍힌 신호입니다! NestJS 서버(Connections: 1)가 기차역에 접속해 메시지를 성공적으로 발행(Publish)한 흔적을 그래프로 확인할 수 있습니다.

     

    ⑤ 메시지 통로(Exchange) 설정 완료

    • 설명: 우리가 코드로 정의한 menu_analysis_exchange가 리스트에 예쁘게 등록되었습니다. 이제 이곳을 통해 분석 데이터들이 FastAPI로 슝슝 날아갈 예정이에요! 🚀

    🏁 마치며

    오늘은 NestJS에서 이미지 업로드를 처리하고 RabbitMQ를 통해 다른 서비스와 통신하는 첫 단추를 끼워보았습니다. 인프라 설정부터 타입 에러까지 쉽지 않았지만, 한 단계씩 뚫어내며 시스템이 견고해지는 것을 느끼니 정말 뿌듯하네요!

     

    다음 포스팅에서는 이 메시지를 받아 실제 분석을 수행할 FastAPI 분석 엔진 구현기로 돌아오겠습니다! 🎀🌻

Designed by Tistory.