본문 바로가기
개발자 도전기/[STUDY] JS || TS

NestJS | Repository pattern

by 답수 2023. 3. 5.
728x90
SMALL

 

 

 

여태까지 작성했던 패턴은 Client(browser) → ... → Controller → Service → DATA Source 순서로 진행됐었고, 실제 비즈니스 로직은 서비스 레이어에서 진행된다.

 

Repository 패턴은 서비스 레이어와 데이터베이스 사이에 레포지토리 레이어 계층이 존재하여 레포지토리가 서비스와 DB를 중계하는 패턴을 말한다.

 

이 패턴은 여러 개의 서비스 레이어가 존재할 때 이점을 가질 수 있다. 예를 들어 A라는 서비스가 있고, 이는 A라는 데이터를 가져온다고 해보자. 이때 B라는 서비스에서 A데이터가 필요해서 A서비스에 접근한다. 그런데 A 역시 마찬가지로 B서비스의 모듈을 참조하는 코드가 있다면 순환 참조가 발생한다(순환 참조에 대해 정리했던 글). 이 문제는 어렵지 않게 해결할 수 있지만 가독성이 떨어진다. 이때 데이터베이스에 접근하는 코드가 분리되어 있다면 A, B 서비스 모듈들이 서로 참조할 필요 없이 레포지토리에만 접근하면 되기 때문에 순환 참조 문제를 깔끔하게 해결할 수 있다. 즉 각각의 서비스 모듈들은 각자의 비즈니스 로직에 더 집중할 수 있게 되고 모듈 간의 책임 분리도 명확해진다.

 

레포지토리 패턴의 핵심은 서비스 레이어에서 데이터의 출처와 관계 없이 동일한 방식으로 데이터에 접근할 수 있게 한다는 것이다. 예컨대 현재 진행하고 있는 프로젝트는 몽고디비만 사용하고 있지만, 만약 MySQL, DynamoDB 등 여러 데이터 출처가 존재한다면, 각각의 DB에 따라 sql 쿼리문이 달라지게 된다. 그런데 레포지토리 모듈에서 각각의 쿼리문들을 정리해서 가지고 있다면 서비스 레이어에서 어떤 데이터를 사용하든지 동일한 방식으로 데이터를 사용할 수 있다. 즉 레포지토리에서 데이터베이스를 중앙통제한다고 생각하면 된다.

 

다른 예시로 몽고디비로 작업하던 프로젝트를 다른 DB로 마이그레이션을 한다고 가정했을 때, 만약 레포지토리 레이어가 없다면 모든 서비스 레이어에서 DB에 접근하는 코드들을 다 바꿔야 하고, 이는 매우 고통스러울 것이다. 하지만 레포지토리 레이어가 존재한다면, 몽구스로 작성되어 있는 레포지토리만 마이그레이션할 DB에 맞게 수정하면 되기 때문에 유지보수 측면에서도 매우 유리하다.

 

 그럼 현재 프로젝트에 레포지토리 패턴을 적용해보자. 먼저 repository 파일을 만든다.

// src/cats/cats.repository.ts

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Cat } from './cats.schema';
import { CatRequestDto } from './dto/cats.request.dto';

@Injectable()
export class CatsRepository {
  constructor(@InjectModel(Cat.name) private readonly catModel: Model<Cat>) {}

  async existsByEmail(email: string): Promise<boolean> {
    const result = await this.catModel.exists({ email });
    return !!result;  // boolean 타입으로 변환
  }

  async create(cat: CatRequestDto): Promise<Cat> {
    return await this.catModel.create(cat);
  }
}

 

이후 cats.serivce에 있던 db에 접근하는 로직들을 레포지토리 모듈의 메서드로 바꿔준다.

// src/cats/cats.service.ts

import { Injectable, UnauthorizedException } from '@nestjs/common';
import * as bcrypt from 'bcrypt';
import { CatRequestDto } from './dto/cats.request.dto';
import { CatsRepository } from './cats.repository';

@Injectable()
export class CatsService {
  constructor(private readonly catsRepository: CatsRepository) {}

  async signUp(body: CatRequestDto) {
    const { email, name, password } = body;
    const isCatExist = await this.catsRepository.existsByEmail(email);

    if (isCatExist) {
      throw new UnauthorizedException('Already exists the cat!');
    }

    const hashedPassword = await bcrypt.hash(password, 10);

    const cat = await this.catsRepository.create({
      email,
      name,
      password: hashedPassword,
    });

    return cat.readOnlyData;
  }
}

 

또한 레포지토리 모듈의 의존성이 주입될 수 있게 모듈의 공급자에 레포지토리 모듈을 추가해준다.

// src/cats/cats.module.ts

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { CatsController } from './cats.controller';
import { CatsRepository } from './cats.repository';
import { Cat, CatSchema } from './cats.schema';
import { CatsService } from './cats.service';

@Module({
  imports: [MongooseModule.forFeature([{ name: Cat.name, schema: CatSchema }])],
  controllers: [CatsController],
  providers: [CatsService, CatsRepository],
})
export class CatsModule {}

 

 

 

 

728x90
LIST

댓글