OOP의 한계
객체지향 프로그래밍은 애플리케이션을 설계할 때 책임과 관심사에 따라 클래스를 분리합니다.
클래스가 단일 책임을 가지도록 분리함으로써 각 모듈의 응집도는 높아지고 결합도는 낮아집니다.
클래스를 변경하는 이유는 오직 한 가지이며, 애플리케이션의 한 부분에서 변경이 발생했을 때 그 파급효과가 시스템의 전체로 퍼져나가는 정도가 낮아집니다.
그러나 전통적인 객체지향 설계 방식을 충실히 따르더라도 한가지 아쉬운 점이 존재합니다.
위 사진처럼, 여러 클래스에 로깅이나 보안 및 트랜젝션 등 공통된 기능들이 흩어져 존재한다는 점입니다.
이렇게 애플리케이션 전반에 걸쳐 흩어져있는 공통되는 부가 기능들을 관심사라고 합니다.
이러한 관심사를 애플리케이션의 핵심 비즈니스 로직 코드로부터 분리하게 하는 방법이 있을 것 입니다.
Transaction 코드
@Service
@RequiredArgsConstructor
public class UserService{
private final UserDao userDao;
private final PlatformTransactionManager transactionManager;
public void sendMoneyToAnotherUser(Long senderId, Long receiverId, Long money) {
TransactionStatus transaction = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
//로깅 관련 로직 추가
//보안 관련 로직 추가
Account senderAccount = userDao.findAccountById(senderId);
Account receiverAccount = userDao.findAccountById(receiverId);
userDao.updateMoney(senderId, senderAccount.withdraw(money));
userDao.updateMoney(receiverId, receiverAccount.add(money));
transactionManager.commit(transaction);
} catch (RuntimeException runtimeException) {
transactionManager.rollback(transaction);
throw runtimeException;
}
}
public void withdrawMoney(Long id, Long money) {
TransactionStatus transaction = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
//로깅 관련 로직 추가
//보안 관련 로직 추가
Account account = userDao.findAccountById(senderId);
userDao.updateMoney(senderId, account.withdraw(money));
transactionManager.commit(transaction);
} catch (RuntimeException runtimeException) {
transactionManager.rollback(transaction);
throw runtimeException;
}
위의 코드에서는 부가 기능 관심사가 트랜잭션 하나 뿐이지만, 로깅이나 보안 등의 관심사가 추가된다면 sendMoneyToAnotherUser() 메소드가 더욱 비대해질 것입니다.
UserService 와 비슷하게 서비스로직 수행 전 트랜잭션의 경계를 지정해주고 로깅이나 보안 등 로직을 수행해야 하는 클래스가 100개 더 있다면 중복되는 코드를 100번 반복해서 작성해야 할 것입니다.
만약 트랜잭션이나 로깅 및 보안 등의 부가 기능의 정책이나 API가 변경된다면 어떻게 될까요?
이를 사용하는 100개의 클래스 모두가 함께 수정되어야 할 것입니다. 유지보수 측면에서 아쉬운 점이 많아질 것입니다.
AOP(Aspect-Oriented Programming)
관점 지향 프로그래밍이란 OOP로 독립적으로 분리하기 어려운 부가 기능을 모듈화하는 방식입니다.
위에서 트랜잭션 관리와 같은 부분이 바로 부가 기능 모듈이며, 이를 Aspect라고 합니다.
핵심 비즈니스 로직을 담고 있지는 않지만 애플리케이션에 부가됨으로써 의미를 갖는 틀별한 모듈입니다.
AOP는 핵심 비즈니스 로직과 부가 기능 Aspect를 분리하는 등 OOP를 보완하는 역할입니다.
AOP 기능을 제공하는 프레임워크나 라이브러리를 사용하면, 번거로운 프록시 클래스 작성없이 UserService 비즈니스 로직에서 트랜잭션이라는 부가 기능 관심사를 간단하게 분리할 수 있습니다.
더불어 다양한 클래스가 Aspect를 재활용하며 공통 사용할 수 있습니다.
Aspect 구성
Aspect는 부가될 기능을 정의한 Advice와, 해당 Advice를 어디에 적용할 지를 결정하는 Pointcut 정보를 가지고 있습니다.
구현 방법
1. Spring AOP를 활용합니다.
2. 프록시를 사용함으로써 부가 기능을 실행합니다.
3. AspectJ를 사용합니다.
4. AspectJ는 컴파일된 타깃의 클래스 파일을 수정하거나, 클래스가 JVM에 로딩되는 시점에 바이트 코드를 조작함으로써 AOP를 적용합니다.
5. 프록시 방식보다 더 다양한 지점에서 부가 기능을 부여할 수 있습니다.
사실 우리가 흔히 보는 @Transactional 어노테이션 또한 AOP가 적용된 대표 사례입니다.
Spring은 @Transactional이라는 어노테이션을 메소드에 부착하면 예외 발생 여부에 따라 해당 트랜잭션을 커밋하거나 롤백합니다.
내부적으로 @Transactional이 붙은 오브젝트에 대해 프록시를 생성하고,
@Transactional로 지정한 메소드를 호출하면 트랜잭션을 선언하겠다는 Pointcut과 Advice를 정보를 바탕으로 부가 기능 관심사를 수행합니다.
다음에는 AopComponent 에 대해서 알아보겠습니다..
참고
https://tecoble.techcourse.co.kr/post/2021-06-25-aop-transaction/
AOP 입문자를 위한 개념 이해하기
이 글은 AOP 개념이 생소한 입문자들을 위한 포스팅입니다. 1. OOP의 한계 image…
tecoble.techcourse.co.kr
댓글