[Node.js_4기] TIL : Nest.js 3_AOP와 캐싱, (24/03/15)
2024. 3. 17. 23:54ㆍ공부/내배캠 TIL
목차
1. 학습 내용
- AOP 개념에 대해서 확실히 이해를 하고 Nest.js에서 AOP 개념을 인터셉터를 활용하여 구현할 수 있습니다.
- 캐시를 적용하여 서버의 성능을 최적화 할 수 있습니다.
2. 내용 정리
04. AOP
04_01) AOP(Aspect-Oriented Programming)
관심사분리 : 여러 부분에서 반복되는 관심사를 분리하고 중앙에서 관리할 수 있게 프로그래밍 하는 것
- 코드의 모듈성을 향상시키고 중복을 줄일 수 있다.
프록시 객체
- JS 내장객체, 다른 객체의 기본 동작을 수정 가능.(로깅, 인증, 에러처리 등...)
헬퍼 객체
- 특정 작업을 수행하는 메서드를 정의하는 것을 통해 코드의 재사용성을 높일 수 있다.
- 공통 기능을 모듈화하고 여러 부분에서 호출 가
전통적 프로그래밍 방식과의 비교
- 전통적 프로그래밍 방식
- 각 방을 따로 만들고 각 방에 개별적으로 문을 설치
- 만약 모든 문의 디자인을 바꾸려면 모든 방의 문을 각각 변경해야함
- AOP 방식
- 문을 설계하고 제작하는 작업을 별도의 공통 모듈에서 수행하고 이를 모든 방에 적용
- 이렇게 하면 문의 디자인을 한 곳에서 변경하면 모든 방의 문이 자동으로 업데이트됩니다.
- 로깅이나 인증, 에러 처리와 같은 기능들은 사실 어플리케이션에서 계속 공통적으로 필요로 하는 기능들
- 이러한 기능들을 각각의 코드 블록에 반복해서 작성하는 것은 비효율적이고 에러를 유발
- 그러므로 AOP 방식을 도입
04_2) 인터셉터
특정 작업을 수행하기 전이나 후에 추가 로직을 실행할 수 있는 코드 블록(AOP 개념을 구현하는 핵심 요소)
- 특히, HTTP 요청과 응답을 처리할 때 특히 유용
- 주로 로깅, 에러 처리, 데이터 변환 및 인증과 같은 공통 관심사를 처리하는 데 사용
05. Nest.js에서의 캐싱
05_1) 캐싱
자주 변하지 않는 데이터에 동일한 요청이 지속적으로 들어오는 경우에 대해서는 캐싱 기능을 사용할 수 있다면 서버의 성능이 전반적으로 올라간다. (캐싱은 사용할 수 있으면 무조건 사용하는 것이 성능을 끌어올릴 수 있다)
- cache-manager와 연계를 하여 캐싱 기능을 사용
3. 예제
04_2) 인터셉터
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const now = Date.now();
return next
.handle()
.pipe(tap(() => console.log(`완료에 걸린 시간: ${Date.now() - now}ms`)));
}
}
NestInterceptor 인터페이스를 구현하는 것이 필수입니다.
- intercept라는 메소드를 통해 요청을 가로챈 후 next.handle()을 호출한다는 것 = 요청을 처리하고 응답을 반환하는 컨트롤러 메소드로 이동을 한다는 뜻(요청이 처리된 이후임)
- pipe()라는 함수로 요청 처리 파이프라인의 다음단계로 이동을 한 후 tap()이라는 연산자 내에서 응답이 처리된 시간을 로그로 찍는 것
05_2) 캐싱
<app.modules.ts>
import { CacheModule } from '@nestjs/cache-manager';
@Module({
imports: [
...
CacheModule.register({
ttl: 60000, // 데이터 캐싱 시간(밀리 초 단위, 1000 = 1초)
max: 100, // 최대 캐싱 개수
isGlobal: true,
}),
....
]
})
register를 할 때 ttl, max, isGlobal 옵션을 설정하여 원하는 방식으로 캐싱 가능
<post.service.ts>
import { Cache } from 'cache-manager';
import _ from 'lodash';
import { Repository } from 'typeorm';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import {
BadRequestException, Inject, Injectable, NotFoundException, UnauthorizedException
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { CreatePostDto } from './dto/create-post.dto';
import { RemovePostDTO } from './dto/remove-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
import { Post } from './entities/post.entity';
@Injectable()
export class PostService {
constructor(
@InjectRepository(Post) private postRepository: Repository<Post>,
@Inject(CACHE_MANAGER) private cacheManager: Cache,
) {}
async create(createPostDto: CreatePostDto) {
return (await this.postRepository.save(createPostDto)).id;
}
async findAll() {
const cachedArticles = await this.cacheManager.get('articles');
if (!_.isNil(cachedArticles)) {
return cachedArticles;
}
const articles = await this.postRepository.find({
where: { deletedAt: null },
select: ['id', 'title', 'updatedAt'],
});
await this.cacheManager.set('articles', articles);
return articles;
}
async findOne(id: number) {
if (_.isNaN(id)) {
throw new BadRequestException('게시물 ID가 잘못되었습니다.');
}
return await this.postRepository.findOne({
where: { id, deletedAt: null },
select: ['title', 'content', 'updatedAt'],
});
}
async update(id: number, updatePostDto: UpdatePostDto) {
if (_.isNaN(id)) {
throw new BadRequestException('게시물 ID가 잘못되었습니다.');
}
const { content, password } = updatePostDto;
const post = await this.postRepository.findOne({
select: ['password'],
where: { id },
});
if (_.isNil(post)) {
throw new NotFoundException('게시물을 찾을 수 없습니다.');
}
if (!_.isNil(post.password) && post.password !== password) {
throw new UnauthorizedException('비밀번호가 일치하지 않습니다.');
}
await this.postRepository.update({ id }, { content });
}
async remove(id: number, removePostDto: RemovePostDTO) {
if (_.isNaN(id)) {
throw new BadRequestException('게시물 ID가 잘못되었습니다.');
}
const { password } = removePostDto;
const post = await this.postRepository.findOne({
select: ['password'],
where: { id },
});
if (_.isNil(post)) {
throw new NotFoundException('게시물을 찾을 수 없습니다.');
}
if (!_.isNil(post.password) && post.password !== password) {
throw new UnauthorizedException('비밀번호가 일치하지 않습니다.');
}
await this.postRepository.softDelete({ id });
}
}
위의 코드는 다음과 같은 흐름을 따른다.
- 캐시를 확인한다
- 캐시가 히트되었다면 캐시를 리턴
- 캐시가 미스되었다면
- Repository를 통해 DB에 접근하여 새로운 결과물을 가져온다
- 결과물을 캐싱한다
- 결과물을 리턴한
4. 생각 정리
해당 TIL을 마지막으로, Nest.js TIL을 마칩니다.
다음 TIL로 프로젝트 회고와 해설 영상을 통해 배운 내용을 정리할 예정입니다.
'공부 > 내배캠 TIL' 카테고리의 다른 글
[Node.js_4기] TIL : 주특기 심화 팀프로젝트 Day2 개발일지 (24/03/19) (0) | 2024.03.19 |
---|---|
[Node.js_4기] TIL : 주특기 심화 팀프로젝트 Day1 개발일지 (24/03/18) (3) | 2024.03.18 |
[Node.js_4기] TIL : Nest.js 2_인증과 인가 (24/03/14) (0) | 2024.03.14 |
[Node.js_4기] TIL : Nest.JS_1 (24/03/06) (0) | 2024.03.06 |
[Node.js_4기] TIL : TypeScript_2 (24/03/05) (4) | 2024.03.05 |