패스트캠퍼스 챌린지

패스트캠퍼스 환급 챌린지 37일차 : 9개 도메인 프로젝트로 끝내는 백엔드 웹 개발 (Java/Spring) 초격차 패키지 Online 강의 후기

ssoonearth 2025. 5. 7. 22:57

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.

https://abit.ly/lisbva

 

 

 

 

패스트캠퍼스의 '9개 도메인 프로젝트로 끝내는 백엔드 웹 개발 (Java/Spring) 초격차 패키지 Online' 강의 수강 37일차!
오늘은 MySQL을 활용한 멱등키 확인 및 응답 기능 구현 작업을 진행하였다.

 

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
}

먼저, 컨트롤러 메소드에서 사용할 수 있도록 @Idempotent 어노테이션을 만들었다.

 

@Getter
@AllArgsConstructor
public class Idempotency {

    private final String key;
    private final Response<?> response;
    
}

멱등키와 응답값을 가지는 Idempotency 객체를 만들어주었다.

 

public interface IdempotencyRepository {
    
    Idempotency getByKey(String key);
    
    void save(Idempotency idempotency);
    
}
@Repository
@RequiredArgsConstructor
public class IdempotencyRepositoryImpl implements IdempotencyRepository {

    private final JpaIdempotencyRepository jpaIdempotencyRepository;

    @Override
    public Idempotency getByKey(String key) {
        Optional<IdempotencyEntity> idempotencyEntity = jpaIdempotencyRepository.findByIdempotencyKey(key);
        return idempotencyEntity.map(IdempotencyEntity::toIdempotency).orElse(null);
    }

    @Override
    public void save(Idempotency idempotency) {
        jpaIdempotencyRepository.save(new IdempotencyEntity(idempotency));
    }
}

멱등키를 통해 멱등성 객체를 조회하는 메소드와 멱등성 객체를 저장하는 Repository를 만든 후, 그 구현체도 만들었다.

 

@Aspect // AOP를 위한 Aspect 클래스임을 나타냄
@Component // Bean으로 등록
@RequiredArgsConstructor
public class IdempotencyAspect {

    private final IdempotencyRepository idempotencyRepository;

	// 현재 요청의 정보를 담고 있는 객체
    private final HttpServletRequest request;

	// 대상 메소드 실행 전후의 작업에 관여하는 기능. @Idempotent가 붙은 메소드에서만 적용됨
    @Around("@annotation(Idempotent)")
    public Object checkIdempotency(ProceedingJoinPoint joinPoint) throws Throwable {
    	// http 요청 헤더에서 멱등키 추출
        String idempotencyKey = request.getHeader("idempotency-key");
        
        // 클라이언트가 멱등키 헤더를 보내지 않은 경우 (== 처음 수행하는 요청의 경우)
        if (idempotencyKey == null) {
            return joinPoint.proceed(); // 로직 그대로 수행
        }
        
        // 멱등키가 있는 경우, 응답값을 가져옴
        Idempotency idempotency = idempotencyRepository.getByKey(idempotencyKey);

        if (idempotency != null) {
            return idempotency.getResponse(); // 로직을 수행하지 않고 저장된 응답값 반환
        }

		// 멱등키는 있지만 저장된 응답값이 없는 경우, 로직을 수행함
        Object result = joinPoint.proceed();
        
        // 멱등성 객체를 생성하고, repository에 저장함
        Idempotency newIdempotency = new Idempotency(idempotencyKey, (Response<?>) result);
        idempotencyRepository.save(newIdempotency);
        
        return result;
    }

}

스프링 AOP를 활용하여 멱등성 객체를 확인하는 클래스를 만들었다.

ProceedingJoinPoint의 proceed() 메소드를 호출하면, 원래 실행하려던 메소드(@Idempotent가 적용된 메소드)를 호출하게 된다.

 

 

 

게시글 좋아요("/like") 컨트롤러 메소드에 @Idempotent 어노테이션을 적용시킨 후 실행하였다.

postman으로 테스트하였고, 임의의 멱등키를 key에 추가하여 요청을 보냈다.

처음 요청을 보냈을 때는 idempotency 테이블에 insert가 되지만, 같은 멱등키를 가진 요청을 보냈을 때는 insert를 수행하지 않고 바로 idempotency 테이블에서 select를 수행하여 저장된 응답값을 반환하는 것을 확인할 수 있었다.

 

이렇게 멱등키를 이용하면 불필요한 조회나 insert 로직의 방해 없이, 같은 요청에 대해 한 번만 수행할 수 있게 된다.

강의에서는 mysql을 사용하였지만, 보통 멱등키는 key-value 형식의 데이터베이스(Redis 등)를 이용하는 것이 좋다고 한다.

 

 

직접 코드로 구현해보며, 기능의 멱등성을 보장하는 방법을 배울 수 있었다.

추가로 잠시 잊고 있던 aop 개념도 다시 한번 떠올리게 되어 좋았다.