Skip to content

Commit

Permalink
[BE] 약속의 UUID 발급 방식을 개선 (#308)
Browse files Browse the repository at this point in the history
* refactor(MeetingService): UUID 생성 방식을 영어 대소문자, 숫자를 포함하는 8글자 랜덤 문자열로 변경

* refactor(MeetingControllerDocs): 스웨거 문서 예외상황 추가

* refactor(UuidGenerator): UUID 생성 로직을 인터페이스 컴포넌트로 분리하여 코드 개선

* refactor(MeetingService): uuid 생성 시 반복문을 for에서 do-while로 개선
  • Loading branch information
seokmyungham authored Aug 22, 2024
1 parent 817b655 commit fc9a753
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@ public interface MeetingControllerDocs {
@ApiErrorResponse.BadRequest(ERROR_CODE_TABLE_HEADER + """
| INVALID_NAME_LENGTH | 이름 길이는 1자 이상 5자 이하 까지 가능합니다. |
| INVALID_PASSWORD_LENGTH | 비밀번호 길이는 1자 이상 10자 이하 까지 가능합니다. |
| PAST_NOT_PERMITTED | 과거 날짜로는 약속을 생성할 수 없습니다. |
""")
@ApiErrorResponse.InternalServerError(ERROR_CODE_TABLE_HEADER + """
| UUID_GENERATION_FAILURE | 약속 생성 과정 중 키 생성에 실패했습니다. 잠시 후 다시 시도해주세요. |
""")
ResponseEntity<MomoApiResponse<MeetingCreateResponse>> create(@RequestBody @Valid MeetingCreateRequest request);


@Operation(
summary = "약속 확정",
description = """
Expand All @@ -49,7 +52,7 @@ public interface MeetingControllerDocs {
""")
@ApiErrorResponse.Unauthorized(ERROR_CODE_TABLE_HEADER + """
| UNAUTHORIZED_TOKEN | 유효하지 않은 토큰입니다. |
""")
""")
@ApiErrorResponse.Forbidden(ERROR_CODE_TABLE_HEADER + """
| ACCESS_DENIED | 접근이 거부되었습니다. |
""")
Expand Down
2 changes: 1 addition & 1 deletion backend/src/main/java/kr/momo/domain/meeting/Meeting.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class Meeting extends BaseEntity {
@Column(nullable = false, length = 20)
private String name;

@Column(nullable = false, length = 40)
@Column(nullable = false, length = 8)
private String uuid;

@Column(nullable = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@
public interface MeetingRepository extends JpaRepository<Meeting, Long> {

Optional<Meeting> findByUuid(String uuid);

boolean existsByUuid(String uuid);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package kr.momo.domain.meeting;

import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.stereotype.Component;

@Component
public class RandomUuidGenerator implements UuidGenerator {

@Override
public String generateUuid(int length) {
return RandomStringUtils.randomAlphanumeric(length);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package kr.momo.domain.meeting;

public interface UuidGenerator {

String generateUuid(int length);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public enum MeetingErrorCode implements ErrorCodeType {
INVALID_DATETIME_RANGE(HttpStatus.BAD_REQUEST, "날짜 또는 시간이 잘못되었습니다."),
PAST_NOT_PERMITTED(HttpStatus.BAD_REQUEST, "과거 날짜로는 약속을 생성할 수 없습니다."),
NOT_CONFIRMED(HttpStatus.NOT_FOUND, "아직 확정되지 않은 약속입니다."),
UUID_GENERATION_FAILURE(HttpStatus.INTERNAL_SERVER_ERROR, "약속 생성 과정 중 키 생성에 실패했습니다. 잠시 후 다시 시도해주세요."),
MEETING_LOAD_FAILURE(HttpStatus.INTERNAL_SERVER_ERROR, "약속 정보를 불러오는데 실패했습니다. 약속을 다시 생성해주세요.");

private final HttpStatus httpStatus;
Expand Down
33 changes: 27 additions & 6 deletions backend/src/main/java/kr/momo/service/meeting/MeetingService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.List;
import java.util.UUID;
import kr.momo.domain.attendee.Attendee;
import kr.momo.domain.attendee.AttendeeRepository;
import kr.momo.domain.attendee.Role;
Expand All @@ -13,6 +12,7 @@
import kr.momo.domain.availabledate.AvailableDates;
import kr.momo.domain.meeting.Meeting;
import kr.momo.domain.meeting.MeetingRepository;
import kr.momo.domain.meeting.UuidGenerator;
import kr.momo.exception.MomoException;
import kr.momo.exception.code.AttendeeErrorCode;
import kr.momo.exception.code.MeetingErrorCode;
Expand All @@ -29,8 +29,12 @@
@RequiredArgsConstructor
public class MeetingService {

private static final int MAX_UUID_GENERATION_ATTEMPTS = 5;
private static final int SHORT_UUID_LENGTH = 8;

private final JwtManager jwtManager;
private final Clock clock;
private final UuidGenerator uuidGenerator;
private final MeetingRepository meetingRepository;
private final AvailableDateRepository availableDateRepository;
private final AttendeeRepository attendeeRepository;
Expand All @@ -49,17 +53,34 @@ public MeetingCreateResponse create(MeetingCreateRequest request) {
return MeetingCreateResponse.from(meeting, attendee, meetingDates, token);
}

private Meeting saveMeeting(String meetingName, LocalTime startTime, LocalTime endTime) {
String uuid = generateUniqueUuid();
Meeting meeting = new Meeting(meetingName, uuid, startTime, endTime);
return meetingRepository.save(meeting);
}

private String generateUniqueUuid() {
String uuid;
int attempts = 0;

do {
uuid = uuidGenerator.generateUuid(SHORT_UUID_LENGTH);
attempts++;
} while (meetingRepository.existsByUuid(uuid) && attempts < MAX_UUID_GENERATION_ATTEMPTS);

if (attempts >= MAX_UUID_GENERATION_ATTEMPTS) {
throw new MomoException(MeetingErrorCode.UUID_GENERATION_FAILURE);
}

return uuid;
}

private void validateNotPast(AvailableDates meetingDates) {
if (meetingDates.isAnyBefore(LocalDate.now(clock))) {
throw new MomoException(MeetingErrorCode.PAST_NOT_PERMITTED);
}
}

private Meeting saveMeeting(String meetingName, LocalTime startTime, LocalTime endTime) {
Meeting meeting = new Meeting(meetingName, UUID.randomUUID().toString(), startTime, endTime);
return meetingRepository.save(meeting);
}

private Attendee saveHostAttendee(Meeting meeting, String hostName, String hostPassword) {
Attendee attendee = new Attendee(meeting, hostName, hostPassword, Role.HOST);
return attendeeRepository.save(attendee);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package kr.momo.domain.meeting.fake;

import kr.momo.domain.meeting.UuidGenerator;

public class FakeUuidGenerator implements UuidGenerator {

private static final String BASE_FAKE_UUID = "Momo";

@Override
public String generateUuid(int length) {
StringBuilder uuid = new StringBuilder();
while (uuid.length() < length) {
uuid.append(BASE_FAKE_UUID);
}

return uuid.substring(0, length);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.mockito.Mockito.doReturn;

import java.time.Clock;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.List;
import kr.momo.domain.attendee.Attendee;
Expand All @@ -16,6 +16,8 @@
import kr.momo.domain.availabledate.AvailableDateRepository;
import kr.momo.domain.meeting.Meeting;
import kr.momo.domain.meeting.MeetingRepository;
import kr.momo.domain.meeting.UuidGenerator;
import kr.momo.domain.meeting.fake.FakeUuidGenerator;
import kr.momo.exception.MomoException;
import kr.momo.exception.code.AttendeeErrorCode;
import kr.momo.exception.code.MeetingErrorCode;
Expand All @@ -30,21 +32,32 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;

@IsolateDatabase
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
class MeetingServiceTest {

private static final Clock FIXED_CLOCK = Clock.fixed(
Instant.parse("2024-08-01T10:15:30Z"), ZoneId.of("Asia/Seoul")
);
@TestConfiguration
static class TestConfig {

@SpyBean
@Bean
public UuidGenerator uuidGenerator() {
return new FakeUuidGenerator();
}

@Bean
public Clock fixedClock() {
return Clock.fixed(Instant.parse("2024-08-01T10:15:30Z"), ZoneId.of("Asia/Seoul"));
}
}

@Autowired
private Clock clock;

@Autowired
private MeetingService meetingService;
private UuidGenerator uuidGenerator;

@Autowired
private MeetingRepository meetingRepository;
Expand All @@ -55,6 +68,9 @@ class MeetingServiceTest {
@Autowired
private AttendeeRepository attendeeRepository;

@Autowired
private MeetingService meetingService;

@DisplayName("UUID로 약속 정보를 조회한다.")
@Test
void findByUUID() {
Expand Down Expand Up @@ -99,11 +115,29 @@ void doesNotFindMeetingSharingMeetingIfUUIDNotExist() {
.hasMessage(MeetingErrorCode.INVALID_UUID.message());
}

@DisplayName("UUID가 이미 존재하여 최대 생성 횟수를 초과하면 예외가 발생한다.")
@Test
void throwExceptionWhenUuidAlreadyExistsAfterMaxAttempts() {
Meeting meeting = new Meeting("momo", uuidGenerator.generateUuid(8), LocalTime.MIDNIGHT, LocalTime.NOON);
meetingRepository.save(meeting);
MeetingCreateRequest request = new MeetingCreateRequest(
"name",
"password",
"meetingName",
List.of(LocalDate.now().toString()),
"08:00",
"22:00"
);

assertThatThrownBy(() -> meetingService.create(request))
.isInstanceOf(MomoException.class)
.hasMessage(MeetingErrorCode.UUID_GENERATION_FAILURE.message());
}

@DisplayName("약속을 생성할 때 과거 날짜를 보내면 예외가 발생합니다.")
@Test
void throwExceptionWhenDatesHavePast() {
//given
setFixedClock();
LocalDate today = LocalDate.now(clock);
LocalDate yesterday = today.minusDays(1);
MeetingCreateRequest request = new MeetingCreateRequest(
Expand Down Expand Up @@ -220,10 +254,4 @@ void throwsExceptionWhenUnlockAttendeeGuest() {
.isInstanceOf(MomoException.class)
.hasMessage(AttendeeErrorCode.ACCESS_DENIED.message());
}

private void setFixedClock() {
doReturn(Instant.now(FIXED_CLOCK))
.when(clock)
.instant();
}
}

0 comments on commit fc9a753

Please sign in to comment.