본문으로 건너뛰기

버그 제보

이상한 점을 알려 주시면 고칠게요. 지금 보고 있는 페이지와 브라우저 정보가 함께 담겨요.

페이지:

Backend

Repository 패턴과 레이어 분리

작성 2026. 10. 13.추가
목차3개 항목

기존 구조는 Controller → Service → DB였다. 리포지토리 패턴은 서비스와 DB 사이에 데이터 접근만 담당하는 중개자(Repository)를 둔다.

flowchart LR
  n1["Controller"] --> n2["Service<br/>(비즈니스 로직)"] --> n3["Repository<br/>(데이터 접근)"] --> n4["DB"]

필수는 아니며, 프로젝트 규모와 상황에 맞게 적용한다.

왜 쓰는가

  1. 데이터 출처와 무관한 일관된 접근: 서비스는 findCatByEmail(email) 같은 메서드만 호출한다. 그 안에서 MongoDB 쿼리를 쓰든 MySQL을 쓰든 서비스는 몰라도 된다. DB를 추가·교체해도 리포지토리만 바꾸면 된다.
  2. 모듈 간 책임 분리: A 서비스가 B 서비스의 데이터를 쓰려고 B 서비스를 주입받고, B도 A를 주입받으면 순환 참조가 생긴다. 공통 데이터 접근을 리포지토리로 빼면 다른 모듈은 리포지토리만 참조하면 되어 각 서비스가 자기 비즈니스 로직에만 집중할 수 있다.
  3. 테스트 용이성: 서비스 테스트 시 리포지토리를 mock으로 바꾸기 쉽다.

구현

// cats/cats.repository.ts
@Injectable()
export class CatsRepository {
  constructor(@InjectModel(Cat.name) private readonly catModel: Model<Cat>) {}
 
  async existsByEmail(email: string): Promise<boolean> {
    // Mongoose 6+의 exists()는 boolean이 아니라 { _id } | null 을 반환한다
    const result = await this.catModel.exists({ email });
    return !!result;
  }
 
  async findCatByEmail(email: string): Promise<Cat | null> {
    return this.catModel.findOne({ email });
  }
 
  async findCatByIdWithoutPassword(id: string): Promise<Cat | null> {
    return this.catModel.findById(id).select('-password');
  }
 
  async create(cat: CatRequestDto): Promise<Cat> {
    return this.catModel.create(cat);
  }
}
// cats.module.ts: 프로바이더로 등록하고, 다른 모듈(Auth)에서도 쓰도록 export
@Module({
  imports: [MongooseModule.forFeature([{ name: Cat.name, schema: CatSchema }])],
  controllers: [CatsController],
  providers: [CatsService, CatsRepository],
  exports: [CatsService, CatsRepository],
})
export class CatsModule {}
// cats.service.ts: Model 대신 Repository를 주입
@Injectable()
export class CatsService {
  constructor(private readonly catsRepository: CatsRepository) {}
}

이렇게 해 두면 AuthService도 CatsService가 아니라 CatsRepository만 주입받아 로그인 검증에 필요한 조회를 할 수 있다. (JWT 인증)

핵심 정리

  • Service = "무엇을 할지"(비즈니스 규칙), Repository = "어떻게 가져올지"(쿼리).
  • 리포지토리도 @Injectable() 프로바이더이므로 모듈에 등록·export해 주입받는다.
  • TypeORM·Prisma 같은 ORM도 같은 개념의 Repository를 제공한다.

관련 노트: DTO와 VO, DAO와 Repository

댓글

GitHub 계정으로 댓글을 남길 수 있어요.