TIL

내일배움캠프 11주차 수요일 TIL

news0516 2025. 1. 8. 23:07

어제 작성한 reviews.repository.js를 프로젝트에서 팀원들끼리 정한 형식에 따라 변경하였다.

// src/repositories/reviews.repository.js

import { prisma } from '../utils/prisma/index.js';

class PostsRepository {
  #orm;

  constructor(orm) {
    this.#orm = orm;
  }

  findAllReviews = async () => {
    // ORM인 Prisma에서 review 모델의 findMany 메서드를 사용해 데이터를 요청합니다.
    const reviews = await this.#orm.review.findMany();

    return reviews;
  };

  findReviewById = async (reviewId) => {
    // ORM인 Prisma에서 review 모델의 findUnique 메서드를 사용해 데이터를 요청합니다.
    const review = await this.#orm.review.findUnique({
      where: { reviewId: +reviewId }, // 수정 필요
    });

    return review;
  };

  createReview = async (restaurantId, paymentId, userId, content, star) => {
    // ORM인 Prisma에서 review 모델의 create 메서드를 사용해 데이터를 요청합니다.
    const createdReview = await this.#orm.review.create({
      data: {
        restaurantId: +restaurantId,
        paymentId: paymentId,
        userId: userId,
        content: content,
        star: +star,
      },
    });

    return createdReview;
  };

  updateReview = async (reviewId, content, star) => {
    // ORM인 Prisma에서 review 모델의 update 메서드를 사용해 데이터를 수정합니다.
    const updatedReview = await this.#orm.review.update({
      where: {
        reviewId: parseInt(reviewId, 10),
      },
      data: {
        ...(content && { content }),
        ...(star && { star }),
      },
    });

    return updatedReview;
  };

  deleteReview = async (reviewId) => {
    // ORM인 Prisma에서 review 모델의 delete 메서드를 사용해 데이터를 삭제합니다.
    const deletedReview = await this.#orm.review.delete({
      where: {
        reviewId: parseInt(reviewId, 10),
      },
    });

    return deletedReview;
  };
}

export default new PostsRepository(prisma);


기존의 코드는 PostsRepository 클래스 내에서 prisma를 직접 사용하는 방식이었는데, 위 코드처럼 PostsRepository 클래스는 orm을 생성자에서 받아서 내부적으로 저장하도록 변경하였다.

#orm은 private 필드로, 클래스 외부에서는 접근할 수 없다.

이때 굳이 private 속성을 사용하는 이유에 대한 의문이 생겨 이에 대해 질문하고 검색을 통해 찾아보았다.

캡슐화 (Encapsulation) : private 속성은 클래스 외부에서 접근할 수 없음.  클래스의 내부 상태를 보호. 외부에서 직접 수정할 수 없게 만든다.

데이터 무결성 (Data Integrity) : 클래스의 내부 데이터가 외부에서 임의로 변경되는 것을 방지(클래스 내부에서면 데이터 수정 가능). 데이터의 일관성과 무결성을 유지

유지보수성 (Maintainability) : 클래스의 내부 구현을 변경하더라도, 외부 코드에 영향을 미치지 않는다(문제 최소화). 유지보수를 용이하게 함

크게 위 같은 이유가 있다.
추가로 대규모 프로젝트일수록 문제가 발생했을 때 클래스 내부만 확인하는, 즉 문제를 국소화한 영역에서 수정할 수 있다거나, 모듈화 되어 팀원들이 독립적으로 개발하고 클래스 이해에 혼란을 줄여준다는 장점도 있다.



API 작성 시 많은 유효성 검증을 동반하게 되는데, 현재처럼 리포지토리, 컨트롤러, 서비스 3 계층으로 구성할 때 각 계층마다 어떤 유효성 검증을 진행하는 것이 적절한지 질문하고 찾아보았다.

질문한 결과 주로 서비스 계층에서 많은 검증 부분을 가지고 있는 것이 일반적이고 컨트롤러 계층에서는 들어오고 나가는 값, 리포지토리계층에서는 db관련 검증이 진행되는 것 확인했다. 
다만 명확하게 구분되어서 공식처럼 정해져 있는 부분은 아닌 듯했다. 그래서 검색과 답변을 통해 리뷰 crud에서 필요한 검증들을 나름대로 구분하여 적용하기로 했다.

1. 리포지토리 계층 
데이터베이스와의 상호작용을 관리하는 계층

- 데이터 변조 검증
db에 저장되기 전 모든 입력값의 형식이 올바른지 확인
ex : 리뷰 저장 전 모든 데이터들의 형식이 설계한 테이블의 칼럼별 타입과 같은지 확인하는 경우

2. 서비스 계층
비즈니스 로직을 처리하며, 컨트롤러와 리포지토리 간 중재 역할.
주로 서비스 계층에서 많은 유효성 검증 수행

- 중복 방지
ex : 결제별 하나의 리뷰만 작성 가능하도록 하여 데이터의 무결성 유지

- 수정 관련 검증

ex : 리뷰 수정 시 변경된 내용이 있는지 확인

- 존재 검증
ex : 수정, 삭제 전 이미 존재하는 리뷰가 있는지 확인

3. 컨트롤러 계층
클라이언트로부터 받은 요청 데이터의 유효성이나 보내는 메시지, 상태코드, 데이터 등을 검증

- 필수 필드 검증
ex : API 실행에 필요한 필드들이 모두 전달되어 있는지 누락 검증

- 별점 유효성 검증
ex :  star가 1-5 사이 값인지 검증



컨트롤러 계층: 필수 필드 및 별점 유효성 검증
서비스 계층: 중복 리뷰 방지, 리뷰 존재 여부 확인, 수정할 필드 검증, 별점 유효성 검증
저장소 계층: 데이터 변조 방지 (선택적)



먼저 기술한 대로 공식처럼 적용할 수는 없는 부분 같아 임의로 진행할 예정.