AOP (관점 지향 프로그래밍)

김동욱·2023년 1월 31일
0

이 팀장: 김길동 개발자님, 각 함수별 실행 시간에 대한 로그를 남겨야 주셔야 할 것 같아요.
김 길동: 네?? 함수별 실행 시간이요? 실행 소요 시간을 말씀하시는 것인가요? 아니면 실행되는 시점에 대한 시간을 말씀하시는 것인가요?
이 팀장: 실행 소요 시간에 대한 로그가 필요합니다.
김 길동: 네 알겠습니다!

... 잠시후 ...

이 팀장: 아니. 김길동 개발자님, 지금 뭐하고 계신거죠?
김 길동: 실행 소요 시간에 대한 로그를 남기라고 하셔서 모든 함수에 관련 코드를 넣고 있는 중 입니다만?
이 팀장: 하.... 공부 다시 해서 오셔야 할 것 같네요... 일단 급하니까 AOP에 대해서 알려드리겠습니다!

김 길동의 코드

public class UserService {
	... skip ...
    
    User findUnique(long id){
    	long start = System.currentTimeMillis();
        try {
        	return userRepository.findOneById(id);
        } finally {
        	long finish = System.currentTimeMillis();
            long timediff = finish - start;
            System.out.println("timediff" + timediff);
        }
    	
    }
    
    void save(CreateUserDTO dto){
    	long start = System.currentTimeMillis();
        try {
        	User data = User.builder().setName(dto.getName()).setEmail(dto.getEmail()).build();
        	void userRepository.save(data);
        } finally {
        	long finish = System.currentTimeMillis();
            long timediff = finish - start;
            System.out.println("timediff" + timediff);
        }	
    }
}

AOP란

AOP는 Aspect Oriented Programming의 약어로 관점 지향 프로그래밍이라는 뜻이다. 즉, 코드를 관점별로 나눠서 모듈화한다. 는 개념입니다.

우리가 모든 함수의 실행 시간을 측정하기로 했죠? 그러면 모든 함수에 반복적으로 시간을 측정하는 로직이 들어가겠죠? 우리는 이렇게 공통되어 반복되게 사용되는 부가적인 로직을 흩어진 관심사 (cross-cutting concerns)라고 해요.

소프트웨어 설계 원칙 공부해보셔서 이미 아실텐데 이렇게 반복되는 로직은 별도로 분리해서 반복 사용해야 한다는 것을 아실거예요. 핵심 로직으로 부터 부가적인 로직을 분리하는 방법이 바로 AOP 입니다.
밑의 그림을 보시면 이해하기가 쉬울 거예요.

해설을 하자면 클래스 A,B,C에서 사용되는 주황 로직을 Aspect X라는 모듈로 분리시키고 클래스 A,C에서 사용되는 파랑 로직을 Aspect Y로, 클래스 A,B에서 사용되는 빨강 로직을 Aspect Z로 분리시킨 것을 볼 수가 있어요.

저희가 처한 문제에 이것을 대입해보자면 findUnique 함수와 save 함수에서 소요 시간을 계산하는 로직을 분리해서 별도의 모듈로 만들어야겠지요?

다행이 Spring Boot에는 이것을 돕는 annotation이 있어요. 우선 코드 먼저 보시죠.

@Aspect
@Component
public class TimeTraceAOP {
	@Around("execution(*hellospringboot..*(..))")
    public Object execute(ProceedingJointPoint jointPoint){
    	long start = System.currentTimeMillis();
    	try {
        	joinPoint.proceed();
        }finally {
        	long finish = System.currentTimeMillis();
            long timediff = finish - start;
            System.out.println("timediff" + timediff);
        }
    }
}

public class UserService {
	... skip ...
    User findUnique(long id){
        return userRepository.findOneById(id);	
    }
    
    void save(CreateUserDTO dto){
        	User data = User.builder().setName(dto.getName()).setEmail(dto.getEmail()).build();
        	void userRepository.save(data);	
    }
}

아까 복잡했던 UserService와 다르게 매우 간단해진 것을 볼 수 있습니다.

@Aspect: 반복적인 부가적 로직을 별도로 분리한 뒤 이 Annotation을 붙이면 해당 로직은 AOP로서 기능할 수 있습니다.

@Around("excecution()"): 해당 관심사가 적용된 범위에 대한 명시입니다. 위에서 저는 hellospringboot..(..) 라는 표현식을 적었는데 hellospringboot 페키지 하위의 모든 함수에 적용하라는 뜻입니다. 만약 UserService에만 적용하고 싶다면 *hellospringboot..*.UserService.*(..) 으로 하면 됩니다.

@Around() 에는 다른 사용법이 있습니다.
@Around("@annotation(TimeTrace)") 로 표기를 하면 @TimeTrace가 붙은 메서드에 Aspect가 적용됩니다.

그러면 아래와 같은 방식으로 구현할 수 있겠죠?

@Aspect
@Component
public class TimeTraceAOP {
	@Around("@annotation(TimeTrace)")
    public Object execute(ProceedingJointPoint jointPoint){
    	long start = System.currentTimeMillis();
    	try {
        	joinPoint.proceed();
        }finally {
        	long finish = System.currentTimeMillis();
            long timediff = finish - start;
            System.out.println("timediff" + timediff);
        }
    }
}

public class UserService {
	... skip ...
    @TimeTrace
    User findUnique(long id){
        return userRepository.findOneById(id);	
    }
    
    @TimeTrace
    void save(CreateUserDTO dto){
        	User data = User.builder().setName(dto.getName()).setEmail(dto.getEmail()).build();
        	void userRepository.save(data);	
    }
}

이 밖에도 @Around 외에 타겟 메서드의 Aspect 실행 시점을 지정할 수 있는 어노테이션이 더 있어요.

@Before (이전) : 어드바이스 타겟 메소드가 호출되기 전에 어드바이스 기능을 수행
@After (이후) : 타겟 메소드의 결과에 관계없이(즉 성공, 예외 관계없이) 타겟 메소드가 완료 되면 어드바이스 기능을 수행
@AfterReturning (정상적 반환 이후): 타겟 메소드가 성공적으로 결과값을 반환 후에 어드바이스 기능을 수행
@AfterThrowing (예외 발생 이후) : 타겟 메소드가 수행 중 예외를 던지게 되면 어드바이스 기능을 수행
@Around (메소드 실행 전후) : 어드바이스가 타겟 메소드를 감싸서 타겟 메소드 호출전과 후에 어드바이스 기능을 수행

이 팀장: 이제 알겠어요? 김길동 개발자님! AOP에 대해서 요약 설명해보세요.
김 길동: AOP는 관심사를 기준으로 코드를 모듈화하여 개발하는 것을 의미합니다. 주로 핵심 기능을 관심사로 하는 코드와 부가적인 기능을 관심사로 하는 코드를 나눠서 모듈화 합니다. 당연히 코드의 유지, 보수에 대한 용이성 향상을 기대할 수 있고요.
이 팀장: 이 정도면 충분히 이해를 시킨 것 같군요. 그러면 코드 다시 짜세요!

profile
nestjs 백엔드 개발합니다.

0개의 댓글