사용자들이 반복적으로 요청하는 데이터나 자주 사용되는 데이터, 빠른 처리가 필요한 데이터 등을 다른 저장장치에 미리 저장해두어 database에 불필요한 요청을 막고 또한 빠르게 resource를 제공할 수 있게 해주는 기술
WAS란 Web Application Server를 뜻하는 말로 database 에서 데이터를 가져오고 로직을 처리하는 서버라고 말하며 NestJs는 NodeJs에서 WAS를 만들게 도와주는 프레임워크 라고 볼 수 있다.
cache-manger 설치
$ npm install @nestjs/cache-manager cache-manager
ioredis 설치
$ npm install cache-manager-ioredis
Redis 연결
[caching.module.ts]
import { CacheModule } from '@nestjs/cache-manager';
import { DynamicModule, Module, Scope } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as Redis from "cache-manager-ioredis";
@Module({})
export class CachingModule {
static register(): DynamicModule {
const cacheModule: DynamicModule = CacheModule.registerAsync({
isGlobal: true,
useFactory: async (configService: ConfigService) => ({
stroe: Redis,
host: configService.get<string>("REDIS_HOST"),
port: configService.get<string>("REDIS_PORT"),
password: configService.get<string>("REDIS_PASSWORD")
}),
inject: [ConfigService]
});
return {
module: CachingModule,
imports: [cacheModule],
exports: [cacheModule],
}
}
}
Redis는 메모리 기반 데이터 스토어로 데이터에 빠르게 접근할 수 있다. 이는 캐시에서 데이터를 검색할 때 빠른 응답 시간을 제공하는 데 큰 장점을 가지고 NestJS에서 Redis를 사용하면 캐싱, 세션 관리 등 다양한 기능을 구현할 수 있다.
webtoon Module
[webtoon.module.ts]
import { Module } from '@nestjs/common';
import { WebtoonController } from './webtoon.controller';
import { WebtoonService } from './webtoon.service';
import { webtoonProvider } from 'src/custom-provider/model.provider';
@Module({
imports: [],
controllers: [WebtoonController],
providers: [WebtoonService, webtoonProvider],
})
export class WebtoonModule {}
CacheModule을 isGlobal: true 옵션을 활성화 했기에 다른 모듈에서 import하지 않아도 사용할 수 있다.
webtoon Service
nest에서 caching을 할 때에는 controller-level에서 할 수도 있지만 다음과 같이 CACHE_MANAGER를 주입해 service-level에서도 구현할 수 있다.
[webtoon.service.ts]
...
constructor(
@Inject("WEBTOON") private webtoonModel: typeof Webtoon,
@Inject(CACHE_MANAGER) private cacheManager: Cache
) {}
async getWebtoonForId(id: string): Promise<Webtoon> {
// cache-key
const webtoonCacheKey: string = `webtoonCache-${id}`;
// cache 값이 있다면 바로 값을 반환
const webtoonCache: string = await this.cacheManager.get(webtoonCacheKey);
if (webtoonCache) {
const webtoonInfo = JSON.parse(webtoonCache);
return new Webtoon(webtoonInfo);
}
// database에서 해당 id의 웹툰 가져오기
const webtoon: Webtoon = await this.webtoonModel.findOne({ where: { webtoonId: id }});
if (!webtoon) {
throw new Error("webtoonId is not exsit..");
}
// cache 값 저장
this.cacheManager.set(
webtoonCacheKey,
JSON.stringify(webtoon),
webtoonCacheTTL, // cache 만료 시간
);
return webtoon;
}
...
위와 같이 service-level에서 구현하지 않고 controller-level에서 구현한다면 훨씬 간단하게 구현할 수 있다.
[webtoon.controller.ts]
import { Controller, Get, Param, UseInterceptors } from '@nestjs/common';
import { WebtoonService } from './webtoon.service';
import { CacheInterceptor, CacheTTL } from '@nestjs/cache-manager';
import { webtoonCacheTTL } from 'src/constatns/redis.constants';
@UseInterceptors(CacheInterceptor)
@Controller('webtoon')
export class WebtoonController {
constructor(private webtoonService: WebtoonService) {}
@CacheTTL(webtoonCacheTTL)
@Get("/:id")
getWebtoonForId(@Param("id") id: string) {
return this.webtoonService.getWebtoonForId(id);
}
}
UseInterceptors 데코레이터만 사용하면 위와 같이 controller-level에서 간단하게 구현할 수 있다. 그렇다면 controller-level에서만 caching을 구현하면 될텐데 왜 service-level에서 구현하는걸까?
service의 종속
Recommend 모듈과 Webtoon 모듈이 있다고 가정했을때 우리는 Recommend모듈에서 Webtoon 모듈의 getWebtoonForId 메서드가 필요할 수 있다. 이럴 때 controller-level에서 caching을 구현했을 때 우리는 service-level에서 caching이 작용하지 않기에 불필요한 database와의 교류가 많이 일어날 수 있다. 그래서 특정 메서드들을 service-level에서 caching을 구현한 것이다.
CacheInterceptor는 Get만 캐싱한다.
controller-level에서 CacheInterceptor로 caching을 구현했을때는 Get Request만 caching을 한다. 즉 POST, PATCH 등은 caching하지 않는다. 하지만 경우에 따라 POST, PATCH에도 caching을 적용하고 싶을 때는 service-level에서 caching을 구현해야 한다.
https://docs.nestjs.com/techniques/caching - NestJs 공식문서