개발자doc 2024. 7. 23. 14:13
목차
1. AOP란?
2. OOP와 차이
3. 어떻게 구현할까?

1. AOP란?
  • Aspect Oriented Programming
  • 관점 지향 프로그램
  • 공통 사항과 핵심 비즈니스 로직을 분리하여 공통 사항을 모듈화하는 프로그래밍 방식

코드를 작성하다 보면 로깅이나 권한 인증같이 중복되는 코드가 발생할 수 있다. 이런 중복되는 코드들을 따로 분리하여 관리하면 전체적인 코드의 양이 줄어드니 가독성이 높아지고 유지보수가 좀 더 편해질 것이다. 

 

1) 개념

▶ 관점(Aspect): 공통의 관심사를 캡슐화하는 모듈입니다.

    ex) 로깅, 보안, 트랜잭션 관리 등 

조인 포인트(Join Point): 코드에서 관점이 적용될 수 있는 지점

포인트컷(Pointcut): 어드바이스가 적용될 조인 포인트를 정의하는 표현식

어드바이스(Advice): 조인 포인트에서 실행되는 코드로, 특정 조인 포인트에서 관점의 동작

조인 포인터와 포인트 컷이 비슷해보이지만 어드바이스가 실행되는 시점을 포인트 컷이라고 하며 포인트 컷이 될 수 있는 모든 지점을 조인 포인트라고 한다.

 

2) 장점

 모듈화: 공통 관심사를 별도의 모듈로 분리하여 코드의 재사용성과 유지보수성을 높인다.

 코드 중복 제거: 여러 모듈에서 반복되는 코드를 제거하고, 공통 로직을 한 곳에서 관리할 수 있다.

 코드 가독성 향상: 비즈니스 로직과 공통 관심사를 분리하여 코드의 가독성을 높일 수 있다.

 

2. OOP와 차이

개발자라면 객체지향적으로 프로그래밍하라는 이야기를 들어봤을 것이다. 그런데 갑자기 관점 지향 프로그래밍이라니...프로그래밍 방식을 완전히 바꿔야할까?

그 두 프로그래밍 방식의 차이를 확인해봐야할 것 같다.

OOP AOP
객체와 클래스 중심으로 설계하면 객체간의 상호작용 중심 프로그래밍 공통의 관심사와 처리를 중심으로 관심사항을 분리하여 모듈화
객체의 상태와 행동으로 모듈화하여 유지보수개선 모듈화된 관심사항으로 코드의 중복을 줄여 유지보수 개선

두 방식은 조금의 차이가 있을 뿐 베타적인 프로그래밍 방식이 아니며 오히려 함께 사용했을 때 단점을 개선할 수 있다.

 

3. 어떻게 구현할까?

1) 스프링에서 구현

@Aspect
@Component
public class TestClass{
    // 언제 호출할건지
    @Around("execution(반환타입 패키지.클래스.메서드(파라미터)")
    public void test(){
        // 작업할 코드 내용
    }
}

스프링에서는 Aspect라는 어노테이션을 사용하여 관점사항임을 나타내고 호출 시점을 지정해준다.

호출 시점은 다음과 같다.

@Before 메서드가 호출되기 전 실행
@After 메서드가 호출된 수 실행
@Around 메서드가 호출되기 전과 후 모두 실행

 

반환 타입이나 모든 메서드 호출은 "*"를 사용과 같이 사용하면된다.

* com.example.TestClass.*(..)

 

 

2) NestJS에서 구현

nestjs에는 AOP를 위한 별도의 데코레이터가 없다. 그렇다면 어떻게 구현을 해야할까?

nestjs에서는 intercepter를 통해 구현이 가능하다. intercepter에서 반환타입은 Observable로 해당 타입을 가져오기 위해서는 rxjs 라이브러리가 필요하다.

npm i rxjs

그 후 인터셉터를 작성한다.

import { CallHandler, ExecutionContext, NestInterceptor } from "@nestjs/common";
import { Observable, pipe, tap } from 'rxjs';
export class TestIntercepter implements NestInterceptor {
    intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    	// 특정 컨트롤러 및 메서드 호출 전 실행할 로직
        return next.handle().pipe(tap(() => {
            // 호출 후 실행할 로직
        }))

    }
}

 

특정 시점에만 실행하려면 해당 시점의 로직만 작성해주면 된다.

import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { AppService } from './app.service';
import { TestIntercepter } from './intercepters/Test.intercepter';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) { }

  @Get()
  @UseInterceptors(TestIntercepter)
  getHello(): string {
    return this.appService.getHello();
  }

}

전체 요청에 실행하려면 컨트롤러 위에, 특정 메서드에만 적용하려면 해당 메서드 위에 UseIntercepter로 지정해주면 된다.