ITDA 프로젝트는 소비자와 쉽게 접근할 여력이 되지 않는 1차산업 종사자 (판매자)와 소비자를 연결해주는 서비스이다.
백엔드단에서 주문하기 기능을 구현한 내용을 정리해보려고 한다.
🧐 고민사항
이용자가 주문 버튼을 두 번 빠르게 눌러서 주문 요청이 두 번 들어올 수 있다. 주문 객체가 두 개가 생겨서 저장되는 문제가 생긴다. 결제까지 이어진다면 큰 문제가 될 수 있다.
이를 어떻게 해결할 것인가?
👉 redis를 이용한다.
redis를 이용하여 구현할 방법이 구체적으로 떠오르지 않았다. 참고할 내용들을 찾아봤다.
검색 결과 중복 요청을 방지하는 방법 이런 글을 보게 되었고 redis에 주문한 유저의 id를 저장하고 만료시간을 지정하면 될 거 같다는 생각을 했다.
장바구니 구현을 이미 redis를 이용해서 redis 관련 세팅은 이미 완료된 상태이다.
key, value 모두 문자열로 저장할 예정이어서 StringRedisTemplate을 사용하기로 했다.
@Service
@RequiredArgsConstructor
public class OrderValidationService {
private static final String REDIS_KEY = "order";
private final StringRedisTemplate stringRedisTemplate;
public boolean isDuplicatedOrder() {
return stringRedisTemplate.opsForValue().get(REDIS_KEY) != null;
}
public void save(Long userId) {
stringRedisTemplate.opsForValue().set(REDIS_KEY, String.valueOf(userId), 1, TimeUnit.SECONDS);
}
}
key: "order", value: "유저의 id 값" 으로 넣고, 만료시간을 1초로 지정하였다.
아래는 주문 기능이 구현된 OrderService 클래스이다.
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class OrderService {
private final OrderValidationService orderValidationService;
// 이외에 필요한 빈들이 추가되어 있음
@Transactional
public OrderResponseDto order(Long userId, OrderRequestDto orderRequest) {
if (orderValidationService.isDuplicatedOrder()) {
throw new OrderDuplicationException();
}
orderValidationService.save(userId);
// 주문 로직
return getOrderResponse(orders);
}
}
주문 로직을 수행하기 전에 OrderValidService의 isDuplicatedOrder()
를 호출해서 중복으로 요청된 주문인지를 확인한다.
만약 1초 이내로 빠르게 들어온 요청이라면 isDuplicatedOrder()
가 true를 반환하여서 예외를 발생시킨다.
이렇게 중복으로 들어온 요청을 방지할 수 있다.
실제로 요청을 넣고 빠르게 레디스에서 키를 확인해보면 order 키가 생긴다. 다시 한번 확인해보면 만료시간이 지나서 사라져 있는 것을 확인할 수 있다.
@SpringBootTest
public class OrderValidationTest {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
@DisplayName("레디스 order 키의 TTL은 1초이다")
void order_redis() throws InterruptedException {
Long userId = 1L;
String redisKey = "order";
stringRedisTemplate.opsForValue().set(redisKey, String.valueOf(userId), 1, TimeUnit.SECONDS);
assertThat(stringRedisTemplate.opsForValue().get(redisKey)).isNotEmpty();
TimeUnit.SECONDS.sleep(2);
assertThat(stringRedisTemplate.opsForValue().get(redisKey)).isNull();
}
}
@SpringBootTest
public class OrderValidationServiceTest {
@Autowired
private OrderValidationService orderValidationService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
@DisplayName("주문 중복 확인 기능 테스트")
void duplicateOrder() {
stringRedisTemplate.opsForValue().set("order", String.valueOf(1L), 1, TimeUnit.SECONDS);
assertThat(orderValidationService.isDuplicatedOrder()).isTrue();
}
}
GitHub repository 👉 https://github.com/Team-IT-DA/Backend