Skip to content

Commit

Permalink
refactor: redisson lock을 어노테이션으로 수정
Browse files Browse the repository at this point in the history
  • Loading branch information
mirageoasis committed Aug 27, 2024
1 parent e95c7b4 commit 598f0e9
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 54 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
package com.thirdparty.ticketing.domain.ticket.service.proxy;

import java.util.concurrent.TimeUnit;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import com.thirdparty.ticketing.domain.common.ErrorCode;
import com.thirdparty.ticketing.domain.common.TicketingException;
import com.thirdparty.ticketing.domain.ticket.dto.request.SeatSelectionRequest;
import com.thirdparty.ticketing.domain.ticket.dto.request.TicketPaymentRequest;
import com.thirdparty.ticketing.domain.ticket.service.ReservationService;
import com.thirdparty.ticketing.global.lock.redisson.RedissonLockAnnotation;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -18,40 +12,18 @@
@Slf4j
public class RedissonReservationServiceProxy implements ReservationServiceProxy {

private final RedissonClient redissonClient;
private final ReservationService reservationService;

private void performSeatAction(String seatId, Runnable action) {
String lockPrefix = "seat:";
RLock lock = redissonClient.getLock(lockPrefix + seatId);

int tryTime = 1;
int releaseTime = 60;

try {
if (!lock.tryLock(tryTime, releaseTime, TimeUnit.SECONDS)) {
return;
}
action.run();
} catch (InterruptedException e) {
throw new TicketingException(ErrorCode.NOT_SELECTABLE_SEAT, e);
} finally {
lock.unlock();
}
}

@Override
@RedissonLockAnnotation(key = "#seatSelectionRequest.seatId")
public void selectSeat(String memberEmail, SeatSelectionRequest seatSelectionRequest) {
performSeatAction(
seatSelectionRequest.getSeatId().toString(),
() -> reservationService.selectSeat(memberEmail, seatSelectionRequest));
reservationService.selectSeat(memberEmail, seatSelectionRequest);
}

@Override
@RedissonLockAnnotation(key = "#ticketPaymentRequest.seatId")
public void reservationTicket(String memberEmail, TicketPaymentRequest ticketPaymentRequest) {
performSeatAction(
ticketPaymentRequest.getSeatId().toString(),
() -> reservationService.reservationTicket(memberEmail, ticketPaymentRequest));
reservationService.reservationTicket(memberEmail, ticketPaymentRequest);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.thirdparty.ticketing.global.config;

import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -25,8 +24,8 @@ public class ReservationServiceContainer {
@Bean
@Primary
public ReservationService redissonReservationServiceProxy(
RedissonClient redissonClient, ReservationRedisService reservationRedisService) {
return new RedissonReservationServiceProxy(redissonClient, reservationRedisService);
ReservationRedisService reservationRedisService) {
return new RedissonReservationServiceProxy(reservationRedisService);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;

import com.thirdparty.ticketing.domain.common.ErrorCode;
Expand All @@ -26,8 +23,6 @@ public class LettuceLockAspect {

@Autowired private LettuceRepository lettuceRepository;

private final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();

private static final String LETTUCE_LOCK_PREFIX = "seat-lock-";

@Around("@annotation(com.thirdparty.ticketing.global.lock.lettuce.LettuceLockAnnotation)")
Expand Down Expand Up @@ -64,17 +59,4 @@ public Object lock(ProceedingJoinPoint joinPoint) throws Throwable {
lettuceRepository.unlock(lockKey);
}
}

private String parseSpel(String spel, ProceedingJoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Object rootObject = joinPoint.getTarget(); // 메서드가 호출된 대상 객체
MethodBasedEvaluationContext context =
new MethodBasedEvaluationContext(
rootObject,
methodSignature.getMethod(), // Method
joinPoint.getArgs(), // Method Arguments
new DefaultParameterNameDiscoverer() // Parameter Name Discoverer
);
return spelExpressionParser.parseExpression(spel).getValue(context, String.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.thirdparty.ticketing.global.lock.redisson;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedissonLockAnnotation {
String key(); // SpEL 표현식으로 Lock 키를 결정

int waitTime() default 1; // 기본 대기 시간

int lockTTL() default 60; // 락의 TTL
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.thirdparty.ticketing.global.lock.redisson;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.thirdparty.ticketing.domain.common.ErrorCode;
import com.thirdparty.ticketing.domain.common.TicketingException;
import com.thirdparty.ticketing.global.lock.CustomSpringELParser;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Aspect
@Component
public class RedissonLockAspect {

@Autowired private RedissonClient redissonClient;

private static final String REDISSON_LOCK_PREFIX = "seat-lock-";

@Around("@annotation(com.thirdparty.ticketing.global.lock.redisson.RedissonLockAnnotation)")
public Object lock(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
RedissonLockAnnotation redissonLockAnnotation =
method.getAnnotation(RedissonLockAnnotation.class);
String lockKey =
REDISSON_LOCK_PREFIX
+ CustomSpringELParser.getDynamicValue(
signature.getParameterNames(),
joinPoint.getArgs(),
redissonLockAnnotation.key());

int waitTime = redissonLockAnnotation.waitTime();
int lockTTL = redissonLockAnnotation.lockTTL();

RLock lock = redissonClient.getLock(lockKey);

try {
if (!lock.tryLock(waitTime, lockTTL, TimeUnit.SECONDS)) {
return false;
}
return joinPoint.proceed();
} catch (InterruptedException e) {
throw new TicketingException(ErrorCode.NOT_SELECTABLE_SEAT, e);
} finally {
lock.unlock();
}
}
}

0 comments on commit 598f0e9

Please sign in to comment.