Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor: 대기열 시스템 로직을 개선한다. #155

Merged
merged 6 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.List;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
Expand All @@ -13,6 +14,13 @@
@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse<Void>> handleAllExceptions(Exception e) {
log.error("예측하지 못한 예외 발생. 메시지={}", e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ErrorResponse.of(ErrorCode.INTERNAL_SERVER_ERROR));
}

@ExceptionHandler(TicketingException.class)
public ResponseEntity<ErrorResponse<Void>> handleTicketingException(TicketingException e) {
ErrorCode errorCode = e.getErrorCode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
public class SeatController {
private final SeatService seatService;

@Waiting
@GetMapping("/performances/{performanceId}/zones/{zoneId}/seats")
public ResponseEntity<ItemResult<SeatElement>> getSeats(@PathVariable("zoneId") long zoneId) {
ItemResult<SeatElement> seats = seatService.getSeats(zoneId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.thirdparty.ticketing.domain.ticket.dto.response.TicketElement;
import com.thirdparty.ticketing.domain.ticket.service.ReservationService;
import com.thirdparty.ticketing.domain.ticket.service.TicketService;
import com.thirdparty.ticketing.domain.waitingsystem.Waiting;

import lombok.RequiredArgsConstructor;

Expand All @@ -41,6 +42,7 @@ public ResponseEntity<Void> releaseSeat(
return ResponseEntity.ok().build();
}

@Waiting
@PostMapping("/seats/select")
public ResponseEntity<Void> selectSeat(
@LoginMember String memberEmail,
Expand All @@ -49,6 +51,7 @@ public ResponseEntity<Void> selectSeat(
return ResponseEntity.ok().build();
}

@Waiting
@PostMapping("/tickets")
public ResponseEntity<Void> reservationTicket(
@LoginMember String memberEmail,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
public class DebounceAspect {

private static final String DEBOUNCE_KEY = "debounce_performance:";
public static final int DEBOUNCE_TIME = 5;

private final ValueOperations<String, String> debounce;

Expand All @@ -40,7 +41,8 @@ public Object debounce(ProceedingJoinPoint joinPoint) throws Throwable {
performanceId = (long) arg;
}
}
if (debounce.setIfAbsent(getDebounceKey(performanceId), "debounce", 10, TimeUnit.SECONDS)) {
if (debounce.setIfAbsent(
getDebounceKey(performanceId), "debounce", DEBOUNCE_TIME, TimeUnit.SECONDS)) {
log.info("[waiting] 디바운스 요청 실행. 공연 ID={}", performanceId);
return joinPoint.proceed();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
package com.thirdparty.ticketing.domain.waitingsystem;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import com.thirdparty.ticketing.domain.common.EventPublisher;
import com.thirdparty.ticketing.domain.waitingsystem.running.RunningManager;
import com.thirdparty.ticketing.domain.waitingsystem.waiting.WaitingManager;
import com.thirdparty.ticketing.domain.waitingsystem.waiting.WaitingMember;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class WaitingSystem {

private final Map<Long, PollingEvent> pollingEventCache = new ConcurrentHashMap<>();
private final WaitingManager waitingManager;
private final RunningManager runningManager;
private final EventPublisher eventPublisher;
Expand All @@ -25,10 +27,12 @@ public void enterWaitingRoom(String email, long performanceId) {
}

public long getRemainingCount(String email, long performanceId) {
WaitingMember waitingMember = waitingManager.findWaitingMember(email, performanceId);
long memberWaitingCount = waitingManager.getMemberWaitingCount(email, performanceId);
long runningCount = runningManager.getRunningCount(performanceId);
long remainingCount = waitingMember.getWaitingCount() - runningCount;
eventPublisher.publish(new PollingEvent(performanceId));
long remainingCount = memberWaitingCount - runningCount;
PollingEvent pollingEvent =
pollingEventCache.computeIfAbsent(performanceId, PollingEvent::new);
eventPublisher.publish(pollingEvent);
if (remainingCount <= 0) {
eventPublisher.publish(new LastPollingEvent(email, performanceId));
}
Expand All @@ -39,9 +43,8 @@ public void moveUserToRunning(long performanceId) {
Set<String> removeMemberEmails = runningManager.removeExpiredMemberInfo(performanceId);
waitingManager.removeMemberInfo(removeMemberEmails, performanceId);
long availableToRunning = runningManager.getAvailableToRunning(performanceId);
Set<WaitingMember> waitingMembers =
waitingManager.pullOutMembers(performanceId, availableToRunning);
runningManager.enterRunningRoom(performanceId, waitingMembers);
Set<String> emails = waitingManager.pullOutMemberEmails(performanceId, availableToRunning);
runningManager.enterRunningRoom(performanceId, emails);
}

public void pullOutRunningMember(String email, long performanceId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@

import java.util.Set;

import com.thirdparty.ticketing.domain.waitingsystem.waiting.WaitingMember;

public interface RunningManager {
boolean isReadyToHandle(String email, long performanceId);

long getRunningCount(long performanceId);

long getAvailableToRunning(long performanceId);

void enterRunningRoom(long performanceId, Set<WaitingMember> waitingMembers);
void enterRunningRoom(long performanceId, Set<String> emails);

void pullOutRunningMember(String email, long performanceId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
public interface WaitingManager {
void enterWaitingRoom(String email, long performanceId);

WaitingMember findWaitingMember(String email, long performanceId);

Set<WaitingMember> pullOutMembers(long performanceId, long availableToRunning);

void removeMemberInfo(String email, long performanceId);

void removeMemberInfo(Set<String> emails, long performanceId);

long getMemberWaitingCount(String email, long performanceId);

Set<String> pullOutMemberEmails(long performanceId, long availableToRunning);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.thirdparty.ticketing.domain.common.EventPublisher;
import com.thirdparty.ticketing.domain.waitingsystem.WaitingAspect;
import com.thirdparty.ticketing.domain.waitingsystem.WaitingSystem;
Expand Down Expand Up @@ -43,15 +42,13 @@ public WaitingManager waitingManager(
}

@Bean
public RedisWaitingRoom waitingRoom(
StringRedisTemplate redisTemplate, ObjectMapper objectMapper) {
return new RedisWaitingRoom(redisTemplate, objectMapper);
public RedisWaitingRoom waitingRoom(StringRedisTemplate redisTemplate) {
return new RedisWaitingRoom(redisTemplate);
}

@Bean
public RedisWaitingLine waitingLine(
StringRedisTemplate redisTemplate, ObjectMapper objectMapper) {
return new RedisWaitingLine(redisTemplate, objectMapper);
public RedisWaitingLine waitingLine(StringRedisTemplate redisTemplate) {
return new RedisWaitingLine(redisTemplate);
}

@Bean
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.util.Set;

import com.thirdparty.ticketing.domain.waitingsystem.running.RunningManager;
import com.thirdparty.ticketing.domain.waitingsystem.waiting.WaitingMember;

import lombok.RequiredArgsConstructor;

Expand All @@ -29,10 +28,9 @@ public long getAvailableToRunning(long performanceId) {
}

@Override
public void enterRunningRoom(long performanceId, Set<WaitingMember> waitingMembers) {
waitingMembers.forEach(WaitingMember::enter);
runningCounter.increment(performanceId, waitingMembers.size());
runningRoom.enter(performanceId, waitingMembers);
public void enterRunningRoom(long performanceId, Set<String> emails) {
runningCounter.increment(performanceId, emails.size());
runningRoom.enter(performanceId, emails);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import java.util.stream.Collectors;

import com.thirdparty.ticketing.domain.waitingsystem.running.RunningRoom;
import com.thirdparty.ticketing.domain.waitingsystem.waiting.WaitingMember;

import lombok.RequiredArgsConstructor;

Expand All @@ -18,7 +17,7 @@ public class MemoryRunningRoom implements RunningRoom {
private static final int MAX_MEMORY_RUNNING_ROOM_SIZE = 100;
private static final int EXPIRED_MINUTE = 5;

private final ConcurrentMap<Long, ConcurrentMap<String, WaitingMember>> room;
private final ConcurrentMap<Long, ConcurrentMap<String, ZonedDateTime>> room;

public boolean contains(String email, long performanceId) {
if (!room.containsKey(performanceId)) {
Expand All @@ -34,13 +33,14 @@ public long getAvailableToRunning(long performanceId) {
- (room.containsKey(performanceId) ? room.get(performanceId).size() : 0));
}

public void enter(long performanceId, Set<WaitingMember> waitingMembers) {
public void enter(long performanceId, Set<String> emails) {
room.compute(
performanceId,
(key, room) -> {
ConcurrentMap<String, WaitingMember> runningRoom =
ConcurrentMap<String, ZonedDateTime> runningRoom =
(room != null) ? room : new ConcurrentHashMap<>();
waitingMembers.forEach(member -> runningRoom.put(member.getEmail(), member));
emails.forEach(
email -> runningRoom.put(email, ZonedDateTime.now().plusSeconds(30)));
return runningRoom;
});
}
Expand All @@ -55,15 +55,15 @@ public void pullOutRunningMember(String email, long performanceId) {
}

public Set<String> removeExpiredMemberInfo(long performanceId) {
ConcurrentMap<String, WaitingMember> performanceRoom = room.get(performanceId);
ConcurrentMap<String, ZonedDateTime> performanceRoom = room.get(performanceId);
if (performanceRoom == null) {
return Set.of();
}

ZonedDateTime fiveMinutesAgo = ZonedDateTime.now().minusMinutes(EXPIRED_MINUTE);
ZonedDateTime now = ZonedDateTime.now();
Set<String> removeMemberEmails =
performanceRoom.entrySet().stream()
.filter(entry -> entry.getValue().getEnteredAt().isBefore(fiveMinutesAgo))
.filter(entry -> entry.getValue().isBefore(now))
.map(Entry::getKey)
.collect(Collectors.toSet());
removeMemberEmails.forEach(performanceRoom::remove);
Expand All @@ -74,14 +74,7 @@ public void updateRunningMemberExpiredTime(String email, long performanceId) {
room.computeIfPresent(
performanceId,
(key, room) -> {
room.computeIfPresent(
email,
(k, waitingMember) ->
new WaitingMember(
email,
performanceId,
waitingMember.getWaitingCount(),
ZonedDateTime.now().plusMinutes(5)));
room.put(email, ZonedDateTime.now().plusMinutes(EXPIRED_MINUTE));
return room;
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.time.ZonedDateTime;
import java.util.Set;
import java.util.stream.Collectors;

import com.thirdparty.ticketing.domain.common.ErrorCode;
import com.thirdparty.ticketing.domain.common.TicketingException;
Expand All @@ -28,18 +29,12 @@ public void enterWaitingRoom(String email, long performanceId) {
}
}

@Override
public WaitingMember findWaitingMember(String email, long performanceId) {
return waitingRoom
.findWaitingMember(email, performanceId)
.orElseThrow(() -> new TicketingException(ErrorCode.NOT_FOUND_WAITING_MEMBER));
}

@Override
public Set<WaitingMember> pullOutMembers(long performanceId, long availableToRunning) {
return waitingLine.pullOutMembers(performanceId, availableToRunning);
}

@Override
public void removeMemberInfo(String email, long performanceId) {
waitingRoom.removeMemberInfo(email, performanceId);
Expand All @@ -49,4 +44,16 @@ public void removeMemberInfo(String email, long performanceId) {
public void removeMemberInfo(Set<String> emails, long performanceId) {
waitingRoom.removeMemberInfo(emails, performanceId);
}

@Override
public long getMemberWaitingCount(String email, long performanceId) {
return waitingRoom.getMemberWaitingCount(email, performanceId);
}

@Override
public Set<String> pullOutMemberEmails(long performanceId, long availableToRunning) {
return waitingLine.pullOutMembers(performanceId, availableToRunning).stream()
.map(WaitingMember::getEmail)
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import com.thirdparty.ticketing.domain.common.ErrorCode;
import com.thirdparty.ticketing.domain.common.TicketingException;
import com.thirdparty.ticketing.domain.waitingsystem.waiting.WaitingMember;
import com.thirdparty.ticketing.domain.waitingsystem.waiting.WaitingRoom;

Expand Down Expand Up @@ -47,4 +49,10 @@ public void removeMemberInfo(Set<String> emails, long performanceId) {
return room;
});
}

public long getMemberWaitingCount(String email, long performanceId) {
return findWaitingMember(email, performanceId)
.map(WaitingMember::getWaitingCount)
.orElseThrow(() -> new TicketingException(ErrorCode.NOT_FOUND_WAITING_MEMBER));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.util.Set;

import com.thirdparty.ticketing.domain.waitingsystem.running.RunningManager;
import com.thirdparty.ticketing.domain.waitingsystem.waiting.WaitingMember;

import lombok.RequiredArgsConstructor;

Expand All @@ -30,9 +29,9 @@ public long getAvailableToRunning(long performanceId) {
}

@Override
public void enterRunningRoom(long performanceId, Set<WaitingMember> waitingMembers) {
runningRoom.enter(performanceId, waitingMembers);
runningCounter.increment(performanceId, waitingMembers.size());
public void enterRunningRoom(long performanceId, Set<String> emails) {
runningCounter.increment(performanceId, emails.size());
runningRoom.enter(performanceId, emails);
}

@Override
Expand Down
Loading
Loading