[#20] 도메인별 예외 분리 #21

Merged
pricelees merged 37 commits from refactor/#20 into main 2025-07-24 02:48:53 +00:00
5 changed files with 47 additions and 33 deletions
Showing only changes of commit 0d33579d3f - Show all commits

View File

@ -11,7 +11,7 @@ import java.time.LocalDate
interface ReservationRepository interface ReservationRepository
: JpaRepository<ReservationEntity, Long>, JpaSpecificationExecutor<ReservationEntity> { : JpaRepository<ReservationEntity, Long>, JpaSpecificationExecutor<ReservationEntity> {
fun findByTime(time: TimeEntity): List<ReservationEntity> fun findAllByTime(time: TimeEntity): List<ReservationEntity>
fun findByDateAndThemeId(date: LocalDate, themeId: Long): List<ReservationEntity> fun findByDateAndThemeId(date: LocalDate, themeId: Long): List<ReservationEntity>

View File

@ -1,13 +1,12 @@
package roomescape.time.business package roomescape.time.business
import org.springframework.data.repository.findByIdOrNull import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional 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.ReservationEntity
import roomescape.reservation.infrastructure.persistence.ReservationRepository import roomescape.reservation.infrastructure.persistence.ReservationRepository
import roomescape.time.exception.TimeErrorCode
import roomescape.time.exception.TimeException
import roomescape.time.infrastructure.persistence.TimeEntity import roomescape.time.infrastructure.persistence.TimeEntity
import roomescape.time.infrastructure.persistence.TimeRepository import roomescape.time.infrastructure.persistence.TimeRepository
import roomescape.time.web.* import roomescape.time.web.*
@ -21,42 +20,33 @@ class TimeService(
) { ) {
@Transactional(readOnly = true) @Transactional(readOnly = true)
fun findById(id: Long): TimeEntity = timeRepository.findByIdOrNull(id) fun findById(id: Long): TimeEntity = timeRepository.findByIdOrNull(id)
?: throw RoomescapeException( ?: throw TimeException(TimeErrorCode.TIME_NOT_FOUND)
ErrorType.TIME_NOT_FOUND,
"[timeId: $id]",
HttpStatus.BAD_REQUEST
)
@Transactional(readOnly = true) @Transactional(readOnly = true)
fun findTimes(): TimeRetrieveListResponse = timeRepository.findAll().toRetrieveListResponse() fun findTimes(): TimeRetrieveListResponse = timeRepository.findAll()
.toResponse()
@Transactional @Transactional
fun createTime(timeCreateRequest: TimeCreateRequest): TimeCreateResponse { fun createTime(request: TimeCreateRequest): TimeCreateResponse {
val startAt: LocalTime = timeCreateRequest.startAt val startAt: LocalTime = request.startAt
if (timeRepository.existsByStartAt(startAt)) { if (timeRepository.existsByStartAt(startAt)) {
throw RoomescapeException( throw TimeException(TimeErrorCode.TIME_DUPLICATED)
ErrorType.TIME_DUPLICATED, "[startAt: $startAt]", HttpStatus.CONFLICT
)
} }
return TimeEntity(startAt = startAt) val time: TimeEntity = request.toEntity()
.also { timeRepository.save(it) }
.toCreateResponse() return timeRepository.save(time).toCreateResponse()
} }
@Transactional @Transactional
fun deleteTime(id: Long) { fun deleteTime(id: Long) {
val time: TimeEntity = findById(id) val time: TimeEntity = findById(id)
reservationRepository.findByTime(time) val reservations: List<ReservationEntity> = reservationRepository.findAllByTime(time)
.also {
if (it.isNotEmpty()) { if (reservations.isNotEmpty()) {
throw RoomescapeException( throw TimeException(TimeErrorCode.TIME_ALREADY_RESERVED)
ErrorType.TIME_IS_USED_CONFLICT, "[timeId: $id]", HttpStatus.CONFLICT }
) timeRepository.delete(time)
}
timeRepository.deleteById(id)
}
} }
@Transactional(readOnly = true) @Transactional(readOnly = true)
@ -66,8 +56,7 @@ class TimeService(
return TimeWithAvailabilityListResponse(allTimes.map { time -> return TimeWithAvailabilityListResponse(allTimes.map { time ->
val isAvailable: Boolean = reservations.none { reservation -> reservation.time.id == time.id } val isAvailable: Boolean = reservations.none { reservation -> reservation.time.id == time.id }
TimeWithAvailabilityResponse(time.id!!, time.startAt, isAvailable) TimeWithAvailabilityResponse(time.id!!, time.startAt, isAvailable)
}) })
} }
} }

View File

@ -0,0 +1,14 @@
package roomescape.time.exception
import org.springframework.http.HttpStatus
import roomescape.common.exception.ErrorCode
enum class TimeErrorCode(
override val httpStatus: HttpStatus,
override val errorCode: String,
override val message: String
) : ErrorCode {
TIME_NOT_FOUND(HttpStatus.NOT_FOUND, "TM001", "시간을 찾을 수 없어요."),
TIME_DUPLICATED(HttpStatus.BAD_REQUEST, "TM002", "이미 같은 시간이 있어요."),
TIME_ALREADY_RESERVED(HttpStatus.CONFLICT, "TM003", "예약된 시간이라 삭제할 수 없어요.")
}

View File

@ -0,0 +1,9 @@
package roomescape.time.exception
import roomescape.common.exception.ErrorCode
import roomescape.common.exception.RoomescapeExceptionV2
class TimeException(
override val errorCode: ErrorCode,
override val message: String = errorCode.message
) : RoomescapeExceptionV2(errorCode, message)

View File

@ -10,6 +10,8 @@ data class TimeCreateRequest(
val startAt: LocalTime val startAt: LocalTime
) )
fun TimeCreateRequest.toEntity(): TimeEntity = TimeEntity(startAt = this.startAt)
@Schema(name = "예약 시간 정보", description = "예약 시간 추가 및 조회 응답시 사용됩니다.") @Schema(name = "예약 시간 정보", description = "예약 시간 추가 및 조회 응답시 사용됩니다.")
data class TimeCreateResponse( data class TimeCreateResponse(
@Schema(description = "시간 식별자") @Schema(description = "시간 식별자")
@ -29,14 +31,14 @@ data class TimeRetrieveResponse(
val startAt: LocalTime val startAt: LocalTime
) )
fun TimeEntity.toRetrieveResponse(): TimeRetrieveResponse = TimeRetrieveResponse(this.id!!, this.startAt) fun TimeEntity.toResponse(): TimeRetrieveResponse = TimeRetrieveResponse(this.id!!, this.startAt)
data class TimeRetrieveListResponse( data class TimeRetrieveListResponse(
val times: List<TimeRetrieveResponse> val times: List<TimeRetrieveResponse>
) )
fun List<TimeEntity>.toRetrieveListResponse(): TimeRetrieveListResponse = TimeRetrieveListResponse( fun List<TimeEntity>.toResponse(): TimeRetrieveListResponse = TimeRetrieveListResponse(
this.map { it.toRetrieveResponse() } this.map { it.toResponse() }
) )
data class TimeWithAvailabilityResponse( data class TimeWithAvailabilityResponse(