refactor: ReservationTimeService 코틀린 전환

This commit is contained in:
이상진 2025-07-18 16:28:43 +09:00
parent d89e662d23
commit b0290e65ff
4 changed files with 82 additions and 92 deletions

View File

@ -1,103 +1,87 @@
package roomescape.reservation.business;
package roomescape.reservation.business
import java.time.LocalDate;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import roomescape.common.exception.ErrorType;
import roomescape.common.exception.RoomescapeException;
import roomescape.reservation.infrastructure.persistence.ReservationEntity;
import roomescape.reservation.infrastructure.persistence.ReservationRepository;
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity;
import roomescape.reservation.infrastructure.persistence.ReservationTimeRepository;
import roomescape.reservation.web.ReservationTimeInfoResponse;
import roomescape.reservation.web.ReservationTimeInfosResponse;
import roomescape.reservation.web.ReservationTimeRequest;
import roomescape.reservation.web.ReservationTimeResponse;
import roomescape.reservation.web.ReservationTimesResponse;
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import roomescape.common.exception.ErrorType
import roomescape.common.exception.RoomescapeException
import roomescape.reservation.infrastructure.persistence.ReservationEntity
import roomescape.reservation.infrastructure.persistence.ReservationRepository
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity
import roomescape.reservation.infrastructure.persistence.ReservationTimeRepository
import roomescape.reservation.web.*
import java.time.LocalDate
@Service
@Transactional
public class ReservationTimeService {
class ReservationTimeService(
private val reservationTimeRepository: ReservationTimeRepository,
private val reservationRepository: ReservationRepository
) {
@Transactional(readOnly = true)
fun findTimeById(id: Long): ReservationTimeEntity = reservationTimeRepository.findByIdOrNull(id)
?: throw RoomescapeException(
ErrorType.RESERVATION_TIME_NOT_FOUND,
"[reservationTimeId: $id]",
HttpStatus.BAD_REQUEST
)
private final ReservationTimeRepository reservationTimeRepository;
private final ReservationRepository reservationRepository;
public ReservationTimeService(
ReservationTimeRepository reservationTimeRepository,
ReservationRepository reservationRepository
) {
this.reservationTimeRepository = reservationTimeRepository;
this.reservationRepository = reservationRepository;
@Transactional(readOnly = true)
fun findAllTimes(): ReservationTimesResponse {
val response = reservationTimeRepository.findAll()
.map { it.toResponse() }
return ReservationTimesResponse(response)
}
@Transactional
fun addTime(reservationTimeRequest: ReservationTimeRequest): ReservationTimeResponse {
validateIsDuplicatedTimeExists(reservationTimeRequest)
return ReservationTimeEntity(startAt = reservationTimeRequest.startAt)
.also { reservationTimeRepository.save(it) }
.toResponse()
}
private fun validateIsDuplicatedTimeExists(reservationTimeRequest: ReservationTimeRequest) {
reservationTimeRepository.findByStartAt(reservationTimeRequest.startAt)
.also {
if (it.isNotEmpty()) {
throw RoomescapeException(
ErrorType.TIME_DUPLICATED, "[startAt: $it]", HttpStatus.CONFLICT
)
}
}
}
@Transactional
fun removeTimeById(id: Long) {
val reservationTime: ReservationTimeEntity = findTimeById(id)
reservationRepository.findByReservationTime(reservationTime)
.also {
if (it.isNotEmpty()) {
throw RoomescapeException(
ErrorType.TIME_IS_USED_CONFLICT, "[timeId: $id]", HttpStatus.CONFLICT
)
}
reservationTimeRepository.deleteById(id)
}
}
@Transactional(readOnly = true)
public ReservationTimeEntity findTimeById(Long id) {
return reservationTimeRepository.findById(id)
.orElseThrow(() -> new RoomescapeException(ErrorType.RESERVATION_TIME_NOT_FOUND,
String.format("[reservationTimeId: %d]", id), HttpStatus.BAD_REQUEST));
fun findAllAvailableTimesByDateAndTheme(date: LocalDate, themeId: Long): ReservationTimeInfosResponse {
val allTimes = reservationTimeRepository.findAll()
val reservations: List<ReservationEntity> = reservationRepository.findByThemeId(themeId)
return ReservationTimeInfosResponse(allTimes
.map { time ->
ReservationTimeInfoResponse(
time.id!!,
time.startAt,
reservations.any { reservation -> reservation.hasSameDateTime(date, time) }
)
}
@Transactional(readOnly = true)
public ReservationTimesResponse findAllTimes() {
List<ReservationTimeResponse> response = reservationTimeRepository.findAll()
.stream()
.map(ReservationTimeResponse::from)
.toList();
return new ReservationTimesResponse(response);
}
public ReservationTimeResponse addTime(ReservationTimeRequest reservationTimeRequest) {
validateTimeDuplication(reservationTimeRequest);
ReservationTimeEntity reservationTime = reservationTimeRepository.save(
new ReservationTimeEntity(null, reservationTimeRequest.startAt)
);
return ReservationTimeResponse.from(reservationTime);
}
private void validateTimeDuplication(ReservationTimeRequest reservationTimeRequest) {
List<ReservationTimeEntity> duplicateReservationTimes = reservationTimeRepository.findByStartAt(
reservationTimeRequest.startAt);
if (!duplicateReservationTimes.isEmpty()) {
throw new RoomescapeException(ErrorType.TIME_DUPLICATED,
String.format("[startAt: %s]", reservationTimeRequest.startAt), HttpStatus.CONFLICT);
}
}
public void removeTimeById(Long id) {
ReservationTimeEntity reservationTime = findTimeById(id);
List<ReservationEntity> usingTimeReservations = reservationRepository.findByReservationTime(reservationTime);
if (!usingTimeReservations.isEmpty()) {
throw new RoomescapeException(ErrorType.TIME_IS_USED_CONFLICT, String.format("[timeId: %d]", id),
HttpStatus.CONFLICT);
}
reservationTimeRepository.deleteById(id);
}
@Transactional(readOnly = true)
public ReservationTimeInfosResponse findAllAvailableTimesByDateAndTheme(LocalDate date, Long themeId) {
List<ReservationTimeEntity> allTimes = reservationTimeRepository.findAll();
List<ReservationEntity> reservations = reservationRepository.findByThemeId(themeId);
List<ReservationTimeInfoResponse> response = allTimes.stream()
.map(time -> new ReservationTimeInfoResponse(time.getId(), time.getStartAt(),
isReservationBooked(reservations, date, time)))
.toList();
return new ReservationTimeInfosResponse(response);
}
private boolean isReservationBooked(List<ReservationEntity> reservations, LocalDate date,
ReservationTimeEntity time) {
return reservations.stream()
.anyMatch(reservation -> reservation.isSameDateAndTime(date, time));
)
}
}

View File

@ -33,7 +33,7 @@ class ReservationEntity(
) {
@JsonIgnore
fun isSameDateAndTime(date: LocalDate?, time: ReservationTimeEntity): Boolean {
fun hasSameDateTime(date: LocalDate?, time: ReservationTimeEntity): Boolean {
return this.date == date && time.startAt == this.reservationTime.startAt
}

View File

@ -111,3 +111,7 @@ data class ReservationTimeResponse(
}
}
}
fun ReservationTimeEntity.toResponse(): ReservationTimeResponse = ReservationTimeResponse(
this.id!!, this.startAt
)

View File

@ -61,7 +61,8 @@ class ReservationTimeServiceTest {
new ReservationTimeEntity(null, LocalTime.of(12, 30)));
// when
Long invalidTimeId = saved.getId() + 1;
assert saved.getId() != null;
long invalidTimeId = saved.getId() + 1;
// when & then
assertThatThrownBy(() -> reservationTimeService.findTimeById(invalidTimeId))
@ -85,6 +86,7 @@ class ReservationTimeServiceTest {
ReservationStatus.CONFIRMED));
// then
assert reservationTime.getId() != null;
assertThatThrownBy(() -> reservationTimeService.removeTimeById(reservationTime.getId()))
.isInstanceOf(RoomescapeException.class);
}