KDT:::Sparta MSA 아키텍처 3기(week03)

2 minute read




1. Spring AOP

1-1. AOP의 개념과 필요성 – 왜 써야 할까?

  • 서비스 코드를 짜다 보면 여러 곳에 똑같이 들어가는 코드들이 있어요.
    • 로그 찍기 (요청 들어왔는지, 어떤 파라미터인지…)
    • 트랜잭션 시작/커밋/롤백
    • 권한 체크
    • 실행 시간 측정
    • 예외 공통 처리
  • 문제점:
    • 코드 중복 증가
    • 비즈니스 로직 가독성 저하
    • 유지보수 어려움
  • AOP는 이러한 공통 기능을 하나의 모듈(Aspect)에 모아서 관리하는 기술이다.

    1-2. 해결책: 관심사의 분리

  • 핵심 로직은 순수하게 유지
  • 공통 기능은 Aspect에 모아 관리
  • 효과
    • 코드 품질 향상
    • 유지보수 개선
    • 중복 제거 (DRY 원칙)


2. AOP 주요 개념

2-1. Aspect (관점)

  • Aspect는 공통 기능을 모아둔 클래스이다.
    @Aspect
    @Component
    public class LoggingAspect { }
    

2-2. Advice (조언)

  • Advice는 언제 무엇을 실행하는지 정의한 메서드이다.
  • 종류
    • @Before : 메서드 실행 전에 할 일
    • @AfterReturning : 정상 종료 후에 할 일
    • @AfterThrowing : 예외 발생 시 할 일
    • @After : 종료 후(성공/실패 상관 없이) 할 일
    • @Around : 앞뒤를 감싸서 전/후/예외까지 직접 제어

2-3. JoinPoint

  • Advice가 실행될 수 있는 지점이다.
  • Spring AOP에서는 메서드 실행 시점만 JoinPoint로 사용한다.
  • JoinPoint 객체로 다음 정보들을 알 수 있어요.
    • 호출된 클래스 / 메서드 이름
    • 전달된 파라미터
    • 리턴 타입 등

2-4. Pointcut

  • Advice를 적용할 대상을 선별하는 필터 역할이다.
  • 수많은 JoinPoint 중 ‘어디에 적용할지’ 고르는 필터
  • execution([수식어] 리턴타입 [클래스경로.]메서드명(파라미터))
    @Pointcut("execution(* com.example.user..*(..))")
    public void userMethods() {}
    

2-5. Pointcut 재사용

  • 같은 조건(예: com.example.user..*)을 여러 Advice에서 쓰고 싶을 때
  • 여러 Pointcut을 조합할 때 ```Java @Pointcut(“execution(* com.example.user..*(..))”) public void userLayer() {}

@Pointcut(“@annotation(LogExecutionTime)”) public void logTimeAnnotated() {}

@Pointcut(“userLayer() && logTimeAnnotated()”) public void combined() {}


<br>

# 3. 리팩토링
* 애플리케이션의 겉으로 보이는 동작은 그대로 유지한 채, 코드의 내부 구조를 개선하는 체계적인 과정입니다.
* 단순히 코드를 정리하는 것을 넘어, 코드의 가독성, 유지보수성, 확장성을 극대화하여 장기적으로 소프트웨어의 가치를 높이는 엔지니어링 활동입니다.

## 3-1. 리팩토리 전 후비교
* Before
```Java
// 구매 처리, 유효성 검사, DB 저장을 모두 수행하는 거대한 메서드
public class PurchaseManager {

    public void processPurchase(Purchase purchase) {
        // 1. 구매 유효성 검사
        if (purchase.getPurchaseId() == null || purchase.getItems().isEmpty()) {
            throw new IllegalArgumentException("잘못된 구매 정보입니다.");
        }

        // 2. 재고 확인
        for (Item item : purchase.getItems()) {
            if (inventory.getStock(item) <= 0) {
                throw new RuntimeException("재고가 부족합니다.");
            }
        }

        // 3. 데이터베이스에 구매 정보 저장
        dbConnection.save(purchase);

        // 4. 이메일 발송
        emailSender.send(purchase.getCustomerEmail(), "구매가 완료되었습니다.");
    }
    
}
  • After ```Java public class PurchaseService {

      // 역할을 위임하여 로직을 명확하게 만듦   private final PurchaseValidator validator;   private final PurchaseRepository repository;   private final NotificationService notificationService;
    

} // 각 클래스는 자신의 책임만 수행한다. class PurchaseValidator { /* 유효성 검사 책임 / } class PurchaseRepository { / 데이터베이스 처리 책임 / } class NotificationService { / 알림 발송 책임 */ } ```