[Spring] AOP(Aspect-Oriented Programming)에 대해서 - 컴도리돌이
개발을 하면서 "이 부분은 코드가 반복되는데, 더 좋은 방법이 없을까"라는 생각을 정말 매 순간 하는 거 같아요. 🥲 그중 하나의 해결 책으로 AOP가 그 해답이 될 수 있어요 🤔
AOP(Aspect-Oriented Programming)
AOP는 프로그램의 특정 동작을 관심사라는 개념으로 분리해서 관리하는 기법이에요. 이게 무슨 말이냐면, 코드에서 여러 곳에서 반복되는 공통된 로직을 한 곳에 모아둔 후, 필요할 때마다 그 로직을 실행하게 할 수 있다는 거예요. 이렇게 하면 코드의 중복을 줄이고, 유지 보수가 훨씬 쉬워지죠 👍
예를 들어, 로깅이나 트랜잭션 관리와 같은 로직은 다양한 메서드에서 공통적으로 사용돼요. 만약 이런 로직을 각 메서드에 직접 작성한다면 코드가 지저분해질 뿐 아니라, 유지보수도 어려워질 거예요. 더 나아가, 만약 모든 메서드에서 로깅 코드를 변경해야 한다면,,, 🫠🫠
이 작업은 아주 번거롭고 시간이 많이 들겠죠?? AOP를 사용하면, 이런 불편한 점을 해결할 수 있어요.
AOP를 사용하면, 로깅이나 트랜잭션 관리와 같은 공통된 기능을 횡단 관심사(Cross-Cutting Concern)로 정의하고, 애플리케이션의 핵심 비즈니스 로직과 분리해서 관리할 숭 있어요. 이렇게 하면 비즈니스 로직은 그 자체에만 집중할 수 있고, 다른 부가적인 로직은 AOP가 알아서 처리해 주죠.
저는 트랜잭션 처리를 할 때 보통 DefaultTransactionDefinition을 사용해 트랜잭션을 수동으로 관리하는 일이 적지 않게 있었어요. 🙂
예를 들어, 새로운 트랜잭션을 시작하고, 작업을 수행한 후 정상적으로 끝나면 커밋하고, 예외가 발생하면 롤백하는 방식이죠. 일반적으로 다음과 같이 사용해요.
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 비즈니스 로직 실행
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
이 코드는 새로운 트랜잭션을 시작하고, 비즈니스 로직을 실행한 후, 성공적으로 완료되면 트랜잭션을 커밋하고, 예외가 발생하면 롤백하는 전형적인 패턴이에요. 이렇게 하면 트랜잭션이 안전하게 관리될 수 있지만, 코드의 중복이 발생하기 쉽고, 비즈니스 로직에 트랜잭션 관리 코드가 섞여 있어 가독성이 떨어질 수 있어요. 🤔
AOP(Aspect-Oriented Programming) 코드 예시
AOP를 통해 트랜잭션 관리 로직을 분리하면, 비즈니스 로직은 트랜잭션에 대한 걱정 없이 깔끔하게 유지할 수 있어요.
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
@Aspect
@Component
public class TransactionAspect {
private final PlatformTransactionManager transactionManager;
public TransactionAspect(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@Around("@annotation(com.example.TransactionalService)")
public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
TransactionStatus status = transactionManager.getTransaction(def);
try {
Object result = joinPoint.proceed();
transactionManager.commit(status);
return result;
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}
이 코드에서는 AOP를 활용해 트랜잭션 관리 로직을 깔끔하게 분리했어요. @Around 어노테이션을 사용해 트랜잭션을 관리할 메서드 호출 전후에 특정 로직을 실행하도록 했죠. @annotaion(com.example.TransactionalService)는 특정 어노테이션이 붙은 메서들만 이 로직이 적용되도록 설정하여, 비즈니스 로직에서는 트랜잭션 관리에 대한 걱정 없이 핵심 기능에만 집중할 수 있게 됩니다. 👍
AOP는 공통 기능을 깔끔하게 분리하고, 비즈니스 로직을 더 효율적으로 관리할 수 있는 멋진 도구인 것 같아요. AOP 덕분에 로깅이나 트랜잭션 관리가 한층 쉬워졌다는 걸 실감했고, 다음에 비슷한 트랜잭션 코드를 작성할 때는 AOP를 적극적으로 도입해 봐야겠어요 🤔