티스토리 뷰

Backend/꾸준히 TIL

[NestJS] Repository Pattern 적용시키기

개발하는 후딘 2022. 11. 11. 14:11
728x90
반응형

[목적]

지난 프로젝트에서는, 서비스 생성자에 엔티티모델을 주입하여

Service 단에서 데이터베이스를 호출하여 비즈니스로직을 만들었다.

 

클라이언트 >>    라우터 + 컨트롤러   >>   서비스 >>   데이터베이스

 

위와 같이 서비스 로직내부에서 데이터베이스를 호출시켜 트랜잭션을 처리하는 것은 여러모로 문제가 있다.

서비스 로직에서 데이터베이스를 호출하여 트랜잭션 처리를 할 수있다만...

비즈니스 로직에 집중하기 어렵다.

비즈니스 로직 자체를 테스트도 어렵고 중복된 코드가 발생한 가능성이 높다.

즉, 비즈니스로직이 길어지기도하고, 가독성이 떨어진다.

 

아주 바보같은 코드군.... if문안에 if를 넣었어.... 리팩토링마렵다.....

// 지난주 프로젝트 : /src/posts/posts.service.ts

import {
  BadRequestException,
  Injectable,
  UnauthorizedException,
  UseFilters,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { InsertResult, Repository } from 'typeorm';
import { Posts } from '../entities/Posts';
import { CreatePostDto } from './dto/create-post.dto';
import { PostType } from '../entities/enums/PostType';
import { Users } from '../entities/Users';
import { PRIVATE_PASSWORD_REGEX } from 'src/common/regex/regex';


@Injectable()
export class PostsService {
  constructor(
    @InjectRepository(Users)
    private readonly usersRepository: Repository<Users>,

    @InjectRepository(Posts)
    private readonly postsRepository: Repository<Posts>,
  ) {}

  async createPost(
    user: Users,
    createPostDto: CreatePostDto,
  ): Promise<InsertResult> {
    if (createPostDto.postType === PostType.PRIVATE_POST) {
      const originPassword = createPostDto.postPassword;
      if (!originPassword) {
        throw new BadRequestException('비밀번호를 입력해주세요');
      }

      // 비밀번호 정규표현식
      const isRegex = originPassword.match(PRIVATE_PASSWORD_REGEX);
      if (!isRegex)
        throw new BadRequestException(
          '비밀번호는 6자리 이상이며, 숫자는 최소 1개가 필요합니다.',
        );

      //입력받은 비밀번호를 암호화
      const hashedPostPassword = await this.hash(originPassword);
      createPostDto.postPassword = hashedPostPassword;
    }

    // Post 추가 ( 서비스 로직 내부에 db를 직접 호출하여 트랜잭션 실행 )
    const result = await this.postsRepository
      .createQueryBuilder('posts')
      .insert()
      .into(Posts)
      .values({ ...createPostDto, userId: user.userId })
      .execute();

    return result;
  }
  
  ...
}

 

 

이번에는 Service 로직 내부에서 트랜잭션을 처리하지 않고

Repository 단을 분리시켜서 트랜잭션 처리하는 것을

이것을 Repository 패턴을 적용시킨다고 한다.

 

클라이언트  >> 컨트롤러 + 라우터  >>  서비스 >>  래포지토리 >>  데이터베이스

 

래포지토리 라는 레이어를 통해서 데이터베이스에 접근하여 트랜잭션을 담당한다.

 

그리고 코드에서도 응집성은 낮추고, 독립성을 높일수록 좋은 코드라고 생각한다.

의존성이 너무 높아지면, 유지보수가 어려워진다. 그러므로 역할에 맞게 레이어를 분리시키는게

더 깔끔하고, 가독성이 높아질것이다!! 서비스 로직에는 비즈니스로직에만 집중하자 !!! 😤

 

자 그러면 한번 래포지토리 레이어를 추가해보자.


[ repository pattern 적용 전]

- market.service.ts

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { CreateMarketDto } from './dto/create-market.dto';
import { UpdateMarketDto } from './dto/update-market.dto';
import { Market, MarketDocument } from './schemas/markets.schema';
import { Product, ProductDocument } from '../products/schemas/product.schema';

@Injectable()
export class MarketsService {
  constructor(
    // 마켓 다큐먼트 주입
    @InjectModel(Market.name) private marketModel: Model<MarketDocument>,

    // 상품 다큐먼트 주입
    @InjectModel(Product.name) private productModel: Model<ProductDocument>,
  ) {}
  
  // 서비스 함수들
}

 

 

[ repository pattern 적용 후 ]

- market.service.ts

마켓서비스가 필요한 것 (MarketRepository) 을 서비스 생성자에 주입한다.

import { Injectable } from '@nestjs/common';
import { CreateMarketDto } from './dto/create-market.dto';
import { UpdateMarketDto } from './dto/update-market.dto';
import { MarketsRepository } from './markets.repository';

@Injectable()
export class MarketsService {
  constructor(private readonly repository: MarketsRepository) {}
  
  // 서비스 함수들
}

 

- market.repository.ts

래포지토리가 필요한 것(MarketDocument, ProductDocument) 를 래포지토리 생성자에 주입(넣는다)

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Market, MarketDocument } from './schemas/markets.schema';
import { Product, ProductDocument } from '../products/schemas/product.schema';

@Injectable()
export class MarketsRepository {
  constructor(
    // 마켓 다큐먼트 주입
    @InjectModel(Market.name) private marketModel: Model<MarketDocument>,

    // 상품 다큐먼트 주입
    @InjectModel(Product.name) private productModel: Model<ProductDocument>,
  ) {}

  // 래포지토리 함수들
  ...
}

 

 

- market.module.ts

import { Market, MarketSchema } from './schemas/markets.schema';
import { MongooseModule } from '@nestjs/mongoose';
import { Product, ProductSchema } from 'src/products/schemas/product.schema';
import { MarketsRepository } from './markets.repository';

@Module({
  imports: [
  	// 모델(몽구스 모듈)
    MongooseModule.forFeature([{ name: Market.name, schema: MarketSchema }]), // 마켓 몽구스 모듈 주입
    MongooseModule.forFeature([{ name: Product.name, schema: ProductSchema }]), // 상품 몽구스 모듈 주입
  ],
  controllers: [MarketsController], // 컨트롤러
  providers: [
    MarketsService, // 서비스
    MarketsRepository, // 래포지토리
  ],
})
export class MarketsModule {}

 

모듈이란 녀석은 외부에서 서비스, 래포지토리, 모델 등과 같은 것들을 주입하도록 해주는 결정자/관리자 역할을 한다.

의존관계가 있는 것들을 관리하는 녀석이다.

그리고 market.module 도 커스텀 모듈이고, 이러한 커스텀 모듈들을 관리하는 녀석이 app.module 이다.

 

 

[의존성 주입]

의존성 주입 (DI, Dependency Injection)

"A 가 B 를 의존한다"  =  "의존대상 B 가 변하면, 그 변화가 A 에 영향을 미친다."

의존관계를 외부에서 결정하고 주입하는 것을 의미한다.

(1) '햄버거 요리사' 는 '햄버거 래시피' 에 의존한다
'햄버거 래시피'의 변화에 따라 '햄버거 요리사'는 햄버거를 만드는 방법을 수정해야한다.

(2) '햄버거 사장님'(외부) 은 '햄버거 래시피'를 결정한다.
어떤 햄버거를 만들지는 '햄버거 사장'이 결정한다.
'햄버거 요리사'가 의존하고 있는 '햄버거 래시피'를 외부에서 결정하고 주입하는 것이다.


의존관계를 분리하여 주입을 받는 방법 인 DI 에는 어떤 장점이 있을까?
1. 의존성이 줄어든다.
2. 재사용성이 높은 코드가 된다.
3. 테스트하기 좋은 코드가 된다.
4. 가독성이 높아진다.

[참고] Repository Pattern

 

[ NestJS ] Repository Pattern에 대하여 알아보자

안녕하세요? 수구리입니다. 이번 포스팅에서는 Nest의 Repository pattern에 대해서 알아보려고 합니다. 디자인 패턴에 대해서 공부하는 것은 굉장히 중요하다고 생각합니다. 어떤 패턴을 쓰냐에 따라

tasddc.tistory.com

 

 

의존관계 주입(Dependency Injection) 쉽게 이해하기

이번 글에서는 DI(의존성 주입, 의존관계 주입)의 개념을 설명한다.

tecoble.techcourse.co.kr

 

- typeorm 의 경우 repository pattern 적용

 

NestJS | API 만들기 (1) - Repository

데이터베이스 connection 방법 Repository Pattern TypeORM이 Repository Pattern을 지원하기 때문에 Entity와 Repository를 사용할 수 있다. Entity @Entity를 포함한 엔티티 관련 데코레이터도 TypeORM이 지원해준다. import

gaemi606.tistory.com

 

앞으로의 숙제

- 디자인패턴 학습하여 정리하기.

 

왜 디자인패턴을 해야되는데?

- 어떤 패턴을 쓰느냐에 따라 코드의 유지보수와 관리의 난이도가 결정된다...😥

 

728x90
반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함