본문 바로가기
Nestjs

[NestJs] Repository 패턴 사용해보기

by 동복이 2024. 3. 20.

Repository 패턴이란?

Repository 패턴은 데이터 소스 간의 중간 계층을 제공하여 데이터 액세스 로직과 비즈니스 로직을 분리하는 디자인 패턴입니다.

이 패턴을 사용하면 애플리케이션이 사용하는 데이터 소스의 실제 구현에 관계없이 일관된 방법으로 데이터를 조작할 수 있습니다.

주요 목적은 데이터 소스에 대한 직접적인 접근 대신 특정 엔티티에 대한 모든 데이터 액세스 로직을 캡슐화하여 애플리케이션과 데이터베이스 사이의 결합도를 낮추는 것입니다.

Repository 패턴의 역할

repository는 데이터 액세스 로직을 처리합니다. 즉 데이터베이스와 관련된 로직을 처리하고 service는 비즈니스 로직을 처리합니다.

 

CRUD 연산 : 기본 데이터베이스 연산을 추상화하고 이를 통해 개발자는 복잡한 쿼리를 작성하지 않고도 필요한 데이터를 쉽게 조작할 수 있습니다.

Entity 관리 : 특정 Entity 타입에 대한 모든 데이터베이스 작업을 관리합니다. 각 Entity에 대한 별도의 Repository를 생성하여 Entity 간의 경계를 명확하게 합니다.

쿼리 로직의 캡슐화 : 복잡한 쿼리 로직을 Repository 내부에 캡슐화함으로써 비즈니스 로직에서는 필요한 데이터를 간단한 메소드 호출로 얻을 수 있습니다.

데이터베이스 세션 관리 : TypeORM에서 Repository는 데이터베이스 연결과 트랜잭션 관리를 담당하여 데이터베이스 작업을 보다 안정적으로 수행할 수 있게 해 줍니다.

 

Repository 패턴을 적용한 간단한 로그인 기능구현

간단한 기능은 Service에서 모든 데이터베이스 요청을 처리하여 사용할 수 있지만 이번 실습에서는 역할을 알아보기 위해 집중하겠습니다.

로그인 기능 같은 경우 인증 및 검증이 필요한 부분이지만 이번 실습에서는 repository 패턴을 알아보기 위해 간단한 로직으로 구현했습니다.

 

// typeorm.config.ts
import { TypeOrmModuleOptions } from '@nestjs/typeorm';

export const TypeOrmConfig: TypeOrmModuleOptions = {
  type: 'postgres',
  host: 'localhost',
  port: 5432,
  username: 'postgres',
  password: 'postgres',
  database: 'sample_db',
  entities: [__dirname + '/../**/*.entity.{js,ts}'],
  synchronize: true,
};

 

// app.module.ts
import { Module, NestModule } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserModule } from './user/user.module';
import { TypeOrmConfig } from './config/typeorm.config';

@Module({
  // TypeOrmConfig 설정을 가져옴
  // 저번시간에는 이부분에 내용을 직접 넣어주는 방식을 사용했지만 이번에는 다른 파일에서 불러오는 방식사용
  imports: [TypeOrmModule.forRoot(TypeOrmConfig), UserModule], 
  controllers: [AppController], 
  providers: [AppService],
})

export class AppModule {}

 

// user.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

// @Entity 데코레이터를 사용하여 이 클래스가 데이터베이스 테이블에 매핑되는 엔티티임을 선언
// 데코레이터에 테이블 이름을 명시적으로 지정하지 않으면 클래스 이름을 카멜 케이스로 자동 변환하여 테이블 이름으로 사용
// 단점으로 테이블 이름 간의 연결을 코드에서 직접 추적해야 하기 때문에 관리 포인트가 늘어날 수 있음
@Entity()
export class User {
  // @PrimaryGeneratedColumn 데코레이터는 이 필드가 기본 키이며 자동으로 생성되는 값을 의미
  @PrimaryGeneratedColumn()
  id: number;
  // @Column 데코레이터는 일반 컬럼을 의미
  // @Column({ unique: true }) 이런식으로도 중복을 방지 할 수 있음
  @Column()
  username: string;

  @Column()
  password: string;
}

 

// user.dto.ts
export class UserDto {
  username: string;
  password: string;
}

 

DTO와 Interface의 차이점

DTO : 계층 간 데이터 교환을 위해 사용되는 객체로 주로 데이터베이스에서 데이터를 가져와 클라이언트에게 전송할 때 사용됩니다.

Interface : 객체의 구조를 정의하는 방법 중 하나로 프로퍼티와 메서드의 이름, 타입을 정의할 수 있으며 클래스가 구현해야 하는 계약 또는 구조글을 명시합니다.

 

차이점 : Interface는 객체의 타입을 정의하는 역할을 하며 주로 컴파일 타임에 타입 체킹을 위해 사용되는 반면 DTO는 런타임에 데이터를 전달하는 데 사용되는 객체로 데이터 전송의 최적화 및 필요한 데이터의 선택적 포함을 위해 사용됩니다.

 

// user.controller.ts
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
import { UserService } from './user.service';
import { UserDto } from './dto/user.dto';
import { User } from './user.entity';

@Controller('user')
export class UserController {
  constructor(private userService: UserService) {}

  // 입력 받은 user 정보를 비즈니스 로직으로 보냄
  @Post('/signup')
  async signUp(@Body() userDto: UserDto): Promise<string> {
    return this.userService.signUp(userDto);
  }

  @Get('/signin')
  async singIn(@Query() userDto: UserDto): Promise<string> {
    return this.userService.signIn(userDto);
  }

  @Get('/list')
  async userList(): Promise<User[]> {
    return this.userService.userList();
  }
}

 

// user.service.ts
import { Injectable } from '@nestjs/common';
import { UserRepository } from './user.repository';
import { UserDto } from './dto/user.dto';

@Injectable()
export class UserService {
  constructor(private userRepository: UserRepository) {}

  // 회원가입 비즈니스 로직
  async signUp(userDto: UserDto) {
    const { username, password } = userDto;
    // 가입되어 있는 유저인지 확인하기 위해 username을 조회
    const findUser = await this.userRepository.findUser(username);

    // 유저 정보가 없을 경우 데이터베이스에 저장
    if (findUser) {
      throw new Error('user already exists');
    } else {
      return this.userRepository.signUp(userDto);
    }
  }
  // 로그인
  async signIn(userDto: UserDto) {
    return this.userRepository.signIn(userDto);
  }
  // 가입된 유저 리스트 
  async userList() {
    return this.userRepository.userList();
  }
}

 

// user.repository.ts
import { Repository } from 'typeorm';
import { User } from './user.entity';
import { UserDto } from './dto/user.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserRepository {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  // 회원가입
  async signUp(userDto: UserDto): Promise<string> {
    const { username, password } = userDto;
    const user = await this.userRepository.create({ username, password });
    await this.userRepository.save(user);
    return 'signup success';
  }

  // 로그인
  async signIn(userDto: UserDto): Promise<string> {
    const { username, password } = userDto;
    const userCheck = await this.userRepository.findOneBy({
      username,
      password,
    });
    if (userCheck) {
      return 'signin success';
    } else {
      return 'signin fail';
    }
  }

  // 같은 유저명을 가진 유저가 있는지 확인
  async findUser(username: string): Promise<User | null> {
    return this.userRepository.findOneBy({ username });
  }

  // 회원가입된 유저 리스트
  async userList(): Promise<User[]> {
    const users = await this.userRepository.find({
      select: ['username'],
    });
    return users;
  }
}

 

회원가입 요청

중복된 아이디로 가입시 에러

 

로그인 시도

 

가입된 유저 정보 

 

 

TypeORM 공식문서상에서 repository 패턴을 사용하는 것으로 보아 이 패턴은 권장되는 것 같은데 어떤 게 정답이다라고 단정 지을 수 없을 거 같습니다.

아직은 파일 간의 의존성 주입이라던지 각 역할의 정의가 애매해 실습하는 동안 우왕좌왕했는데 이번 실습을 계기로 역할 및 의존성에 대해 알게 되어 뜻깊은 실습이 됐습니다.

728x90

'Nestjs' 카테고리의 다른 글

[NestJs] 클린 코드 알아보기  (0) 2024.03.28
[NestJs] 로그인 기능 구현  (0) 2024.03.23
[NestJs] TypeORM 연결하기  (3) 2024.03.18
[NestJs] Middleware 알아보기  (1) 2024.03.15
[NestJs] Interceptors 알아보기  (0) 2024.03.14