-
Notifications
You must be signed in to change notification settings - Fork 1
인터셉터로 관심사 분리하기
mintaek edited this page Dec 1, 2024
·
7 revisions
NestJS의 인터셉터는 요청과 응답 흐름 양쪽에서 호출되는 독특한 컴포넌트로, 공통적인 작업을 처리하거나 비즈니스 로직과 중복되는 코드를 효과적으로 분리하는 데 적합합니다.
이 특성을 활용하면 데이터 전처리, 로깅, 모니터링, 캐싱뿐만 아니라, 트랜잭션 관리나 DB 연결과 같은 반복 로직도 인터셉터로 집중화할 수 있습니다.
NestJS에서 인터셉터는 다음과 같은 상황에서 호출됩니다:
- 요청(Request): 컨트롤러에 전달되기 전에 데이터를 처리하거나 설정 작업을 수행.
- 응답(Response): 컨트롤러에서 반환된 결과를 가공하거나 후처리 작업을 수행.
이런 특징은 요청과 응답 모두에서 중복 로직을 최소화하고, 비즈니스 로직과 공통 작업을 분리하여 관심사 분리를 실현할 수 있게 합니다.
아래는 인터셉터 없이 DB 연결 및 트랜잭션 관리 로직을 서비스와 컨트롤러에서 처리한 경우 입니다.
sequenceDiagram
participant 사용자 as 사용자(Client)
participant 컨트롤러 as Controller
participant 서비스 as UserDBService
participant 쿼리서비스 as QueryService
participant 데이터베이스 as Database
사용자->>컨트롤러: 요청 전송 (sessionID 포함)
컨트롤러->>서비스: 요청 전달
서비스->>데이터베이스: 세션 ID로 DB 연결 생성
alt 연결 성공
서비스->>데이터베이스: 트랜잭션 시작
서비스->>쿼리서비스: 비즈니스 로직 전달
쿼리서비스->>데이터베이스: 쿼리 실행
alt 쿼리 성공
쿼리서비스->>서비스: 실행 결과 반환
서비스->>데이터베이스: 트랜잭션 커밋
서비스->>컨트롤러: 처리 결과 반환
컨트롤러->>사용자: 최종 응답 반환
else 쿼리 실패
쿼리서비스->>서비스: 에러 반환
서비스->>데이터베이스: 트랜잭션 롤백
서비스->>컨트롤러: 에러 반환
컨트롤러->>사용자: 에러 응답 반환
end
else 연결 실패 (에러 1040)
서비스->>컨트롤러: "현재 사용자가 많습니다" 에러 반환
컨트롤러->>사용자: 에러 응답 반환
end
서비스->>데이터베이스: DB 연결 종료
문제점
- 중복 코드: 컨트롤러와 서비스마다 DB 연결, 트랜잭션 관리, 에러 처리 코드가 반복됨.
- 비즈니스 로직 혼재: 비즈니스 로직과 시스템 관리 로직(DB 연결, 에러 처리 등)이 섞여 있어 가독성과 유지보수성이 떨어짐.
- 확장성 부족: 공통 작업(예: 로깅, 모니터링)을 추가하려면 모든 서비스와 컨트롤러를 수정해야 함.
인터셉터로 관심사 분리
인터셉터를 사용하면 위 문제를 해결할 수 있습니다. 데이터베이스 연결과 트랜잭션 관리를 인터셉터로 옮기면 서비스와 컨트롤러는 비즈니스 로직에만 집중할 수 있습니다.
sequenceDiagram
participant 사용자 as 사용자(Client)
participant 인터셉터 as UserDBConnectionInterceptor
participant 데이터베이스 as Database
participant 컨트롤러 as Controller
사용자->>인터셉터: 요청 전송 (sessionID 포함)
인터셉터->>데이터베이스: 세션 ID로 DB 연결 생성
alt 연결 성공
인터셉터->>데이터베이스: 트랜잭션 시작
인터셉터->>컨트롤러: 요청 전달
컨트롤러->>인터셉터: 응답 반환
인터셉터->>데이터베이스: 트랜잭션 커밋
else 연결 실패 (에러 1040)
인터셉터->>사용자: "현재 사용자가 많습니다" 에러 반환
end
alt 컨트롤러에서 예외 발생
인터셉터->>데이터베이스: 트랜잭션 롤백
인터셉터->>사용자: 에러 응답 반환
end
인터셉터->>데이터베이스: DB 연결 종료
인터셉터->>사용자: 최종 응답 반환
@Injectable()
export class UserDBConnectionInterceptor implements NestInterceptor {
constructor(private readonly configService: ConfigService) {}
async intercept(
context: ExecutionContext,
next: CallHandler,
): Promise<Observable<any>> {
const request = context.switchToHttp().getRequest();
const identify = request.sessionID;
try {
request.dbConnection = await createConnection({
host: this.configService.get<string>('QUERY_DB_HOST'),
user: identify.substring(0, 10),
password: identify,
port: this.configService.get<number>('QUERY_DB_PORT', 3306),
database: identify,
infileStreamFactory: (path) => {
return createReadStream(path);
},
});
} catch (error) {
console.error('커넥션 제한으로 인한 에러', error);
if (error.errno == 1040) {
throw new HttpException(
{
status: HttpStatus.TOO_MANY_REQUESTS,
message: 'Too many users right now! Please try again soon.',
},
HttpStatus.TOO_MANY_REQUESTS,
);
}
}
await request.dbConnection.query('set profiling = 1');
await request.dbConnection.beginTransaction();
return next.handle().pipe(
tap(async () => {
await request.dbConnection.commit();
}),
catchError(async (err) => {
if (err instanceof DataLimitExceedException) {
await request.dbConnection.rollback();
throw err;
}
}),
finalize(async () => await request.dbConnection.end()),
);
}
}