[Nest.js] cache config를 동적으로 로드하고 cache service를 주입받아 사용해보자.

Donghun Seol·2023년 4월 23일
0

0. 배경

express와 typescript로 작성된 cds프로젝트의 api를 nest.js로 변경하는 작업을 진행중이다.

1. 요구사항

api에 대한 e2e 테스트를 작성하기 전에 auth가드를 작성해야 한다. 그런데 인증은 AWS elasticache에 세션 id를 저장해놓는 방식으로 구현되어 있다. 그러므로 캐시DB도 연결해야 하는데 AWS elasticache에 VPC 밖에서 접근하는건 좀 까다롭다.

따라서 개발환경에는 nest.js의 기본 CacheModule을 사용하고 배포환경에서는 AWS elasticache를 사용하도록 동적인 캐시설정을 구현하려 한다.

2. 에러

처음엔 CacheModuleOptions의 store 속성에 전달하는 redisStore를 "cache-manager-redis-store": "^3.0.1"에서 가져왔는데 타입 에러가 났다. 직방 기술블로그에서 사용한 "cache-manager-ioredis": "^2.1.0",패키지를 활용해서 해결했다.

3. 성공

CacheModule.registerAsync()를 활용했다.

app.module.ts

import { cacheConfig } from 'config/cache.config';

@Module({
  imports: [
    ConfigModule.forRoot({
      envFilePath: [`${__dirname}/config/env/.${process.env.NODE_ENV}.env`],
      load: [],
      isGlobal: true,
      validationSchema: validationSchema,
    }),
    CacheModule.registerAsync(cacheConfig),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

주입받은 configService의 NODE_ENV 값에 따라 동적으로 캐싱DB가 바뀐다.
cache.config.ts

import { CacheModuleOptions } from '@nestjs/cache-manager';
import { ConfigModule, ConfigService } from '@nestjs/config';
import * as redisStore from 'cache-manager-ioredis';

export const cacheConfig = {
  imports: [ConfigModule],
  useFactory: (configService: ConfigService) => {
    const option: CacheModuleOptions = {
      ttl: 1000,
    };
    if (configService.get('NODE_ENV') === 'development') {
      return option;
    }
    console.log('ENV is not development');
    option.store = redisStore;
    option.host = configService.get('REDIS_HOST');
    option.port = configService.get('REDIS_PORT');
    return option;
  },
  inject: [ConfigService],
};

4. 레퍼런스

직방 기술블로그
https://medium.com/zigbang/nestjs%EC%9D%98-module%EA%B3%BC-cachemodule%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-redis-%EC%97%B0%EB%8F%99-2166a771196

5. 새로운 문제와 개선

위와 같은 형태로 app.module.ts에서 캐시모듈을 직접 설정해서 활용하니 user controllerswap controller와 같은 다른 모듈에서 캐시모듈을 주입받아 활용하기 불편한 단점이 있었다.

따라서 MyCacheModule과 같은 형태로 캐시모듈을 분리했다. 그 뒤 MyCacheModule에서 export하는 MyCacheService와 같은 서비스 provider를 다른 모듈에서 주입받을 수 있게 설정했다.

이렇게 되면 모듈별로 역할과 책임이 명확히 분리되어 가독성과 유지보수성이 향상되고, 캐시를 사용할 모듈은 서비스만 간단히 주입받아 활용하면된다.
(만약 캐시관련 새로운 로직을 추가해야한다면 cache.service.ts 파일과 호출하는 컨트롤러만 수정하면 된다.)

cache.module.ts

import { Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';
import { cacheConfig } from 'config/cache.config';
import { MyCacheService } from './cache.service';

@Module({
  imports: [CacheModule.registerAsync(cacheConfig)],
  providers: [MyCacheService],
  exports: [MyCacheService],
})
export class MyCacheModule {}

cache.service.ts

import { Injectable, Inject } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';

@Injectable()
export class MyCacheService {
  constructor(@Inject(CACHE_MANAGER) private readonly cacheManager: Cache) {}

  async get(key: string): Promise<any> {
    return await this.cacheManager.get(key);
  }

  async set(key: string, value: any, option?: any) {
    await this.cacheManager.set(key, value, option);
  }

  async reset() {
    await this.cacheManager.reset();
  }

  async del(key: string) {
    await this.cacheManager.del(key);
  }
}
profile
I'm going from failure to failure without losing enthusiasm

0개의 댓글