내배캠 100일차!

·2023년 2월 21일
0

내일배움캠프

목록 보기
110/142
post-thumbnail

커스텀 리포지토리

커스텀 리포지토리가 왜 필요한가요?

일반 리포지토리 기능을 상속받은 커스텀 쿼리가 가능한 리포지토리

일반 리포지토리로도 기본적인 CRUD를 해야하지만 경우에 따라서는 더 복잡한 연산 및 쿼리를 직접 정의해야 하는 경우가 있습니다. 그럴 때는, 일반 리포지토리를 상속받은 커스텀 리포지토리를 작성하여 해결할 수 있어요!

커스텀 리포지토리 만들기

클라이언트가 조회수 순서대로 게시글을 조회하고 싶다고 요청!

article.entity.ts

Article 엔티티에 view 컬럼을 추가

@Column("int")
  view: number; // 새로 추가된 컬럼!

article.repository.ts(커스텀 리포지토리)

import { Injectable } from "@nestjs/common";
import { DataSource, Repository } from "typeorm";
import { Article } from "./article.entity";

@Injectable()
export class ArticleRepository extends Repository<Article> {
  constructor(private dataSource: DataSource) {
    super(Article, dataSource.createEntityManager());
  }

  async getArticlesByViewCount() {
    const result = await this.createQueryBuilder()
      .select("article")
      .from(Article, "article")
      .orderBy("article.view", "DESC")
      .getMany();
    return result;
  }
}

board.module.ts

providers: [BoardService, ArticleRepository], // ArticleRepository 추가!

board.controller.ts

getHotArticles라는 새로운 함수 추가. /hot-articles라는 엔드포인트를 호출하면 인기가 많은 순으로 게시물을 가져오는 함수!

// 새롭게 추가한 API
  @Get("/hot-articles")
  async getHotArticles() {
    return await this.boardService.getHotArticles();

board.service.ts

// 일반 리포지토리엔 없는 커스텀 리포지터리에만 있는 함수!
  async getHotArticles() {
    return await this.articleRepository.getArticlesByViewCount();
  }

캐싱 사용해보기

자주 변하지 않는 데이터에 동일한 요청이 지속적으로 들어오는 경우에 대해서는 캐싱 기능을 사용할 수 있다면 서버의 성능이 전반적으로 올라감!

Nest.js에서는 cache-manager와 연계를 하여 캐싱 기능을 사용할 수 있음

cache-manager 설치

npm i cache-manager
npm i -D @types/cache-manager

app.module.ts

imports에 추가

CacheModule.register({
      ttl: 60000, // 데이터 캐싱 시간(밀리 초 단위, 1000 = 1초)
      max: 100, // 최대 캐싱 개수
      isGlobal: true,
    }),

board.service.ts

캐시 매니저를 DI 해보겠습니다. BoardService에서 getArticles 함수에 캐시 매니저를 사용할 것! 서비스 코드를 다음과 같이 바꿔주면 됩니다.

constructor(
    // 새로 의존성을 주입한 캐시 매니저!
    @Inject(CACHE_MANAGER) private readonly cacheManager: Cache,
    private articleRepository: ArticleRepository
  ) {}

async getArticles() {
    const cachedArticles = await this.cacheManager.get("articles");
    if (!_.isNil(cachedArticles)) {
      return cachedArticles;
    }

    const articles = await this.articleRepository.find({
      where: { deletedAt: null },
      select: ["author", "title", "updatedAt"],
    });
    await this.cacheManager.set("articles", articles);
    return articles;
  }

“articles”라는 키를 통해 this.cacheManager.get 함수를 통해서 캐시를 뒤져보고 캐싱이 되었다면 캐싱된 결과(cachedArticles)를 리턴하고 그렇지 않으면 리포지토리로 결과 값을 가져온 뒤 this.cacheManager.set 함수를 통해서 캐싱을 한 후 결과물을 리턴하는 것!

캐싱을 적용해도 크게 문제가 없는 서비스 로직에 한해서는 적극적으로 캐싱을 적용하는 것을 추천합니다! 어차피, max 속성을 통해 캐싱이 되는 데이터 양도 조절을 할 수 있어 메모리가 오버플로우 날 걱정은 딱히하지 않아도 됩니다. 또한, 지금은 인-메모리 기반으로 캐싱을 했지만 Redis와 같은 인-메모리 데이터베이스를 사용하는 것도 가능하니 항상 적극적으로 고려해주세요!

Rate limit 적용

특정 유저(IP 기준)가 짧은 시간내에 여러번 같은 API를 호출하는 것을 방지

@nestjs/throttler 설치

npm i @nestjs/throttler

app.module.ts

imports: [
ThrottlerModule.forRoot({
      ttl: 60,
      limit: 10, // ttl 동안 limit 만큼의 요청만 받는다.
    }),
 ],
providers: [
    AppService,
    AuthMiddleware,
    {
      provide: APP_GUARD,
      useClass: ThrottlerGuard,
    },
  ],

ThrottlerModule.forRoot 함수를 통해서 Rate limit 옵션을 글로벌하게 적용해줍니다. limit 이상의 요청을 하게 되면 요청에 응답을 하지 않습니다.

또한, imports 뿐 아니라 providers에도 추가적으로 코드를 작성해주어 글로벌하게 Rate limit을 적용시키도록 합니다.

@SkipThrottle()

모든 API도 Rate limit 기능이 적용이 되었기 때문에 DoS 공격으로부터 보호받을 수 있습니다. 하지만, 경우에 따라서는 특정 API는 Rate limit 기능 적용을 하고 싶지 않은 경우도 있겠죠? 그럴때는 @SkipThrottle() 데코레이터를 API위에 명시해주면 됩니다.

@SkipThrottle() // 데코레이터 추가!
@Get("/articles")
async getArticles() {
  return await this.boardService.getArticles();
}

@Throttle(limit, ttl)

Rate limit 정책을 특별하게 적용하고 싶을 때 사용

@Throttle(5, 60) // 이렇게 하면 60초에 5번 호출만 가능!
@Get("/articles/:id")
async getArticleById(@Param("id") articleId: number) {
  return await this.boardService.getArticleById(articleId);
}
profile
개발자 꿈나무

0개의 댓글