Skip to content

Comments

[FEAT] 예약 도메인 개발 (#283)#284

Merged
redblackblossom merged 8 commits intomainfrom
feat/#283/reservation_domain
Feb 12, 2026
Merged

[FEAT] 예약 도메인 개발 (#283)#284
redblackblossom merged 8 commits intomainfrom
feat/#283/reservation_domain

Conversation

@redblackblossom
Copy link
Contributor

@redblackblossom redblackblossom commented Feb 12, 2026

📌 관련 이슈

✨ PR 세부 내용

배경

  • MSA 전환 과정에서 결제 연동 전에 예약 코어 도메인(선점/확정/취소/만료) 정의가 먼저 필요했습니다.
  • 예약의 핵심 상태 전이와 불변식을 엔티티/VO로 먼저 고정해, 이후 결제/사가 연동 시 일관성을 확보하고자 했습니다.

변경 내용

1) Reservation Aggregate Root 추가

  • Reservation 엔티티 신규 추가
  • 예약 상태 모델 정의: PENDING, CONFIRMED, CANCELED, EXPIRED
  • 주요 도메인 행위 추가
  • hold(...): 임시 예약 생성
  • confirm(...): 결제 성공 후 확정
  • cancel(...): 모델/작가/시스템 취소
  • expire(...): 홀드 만료 처리
  • isPayableAt(...), isHoldExpiredAt(...): 결제/만료 판단
  • @Version 기반 낙관적 락 적용으로 동시성 제어 기반 마련

2) 예약 도메인 Value Object/Enum 추가

  • Enum: ReservationStatus, CanceledBy
  • VO: ReservationNumber, ReservationTimeSlot, Money, CancelReason, CancellationInfo
  • 각 VO에 도메인 유효성 검증 및 불변성/동등성 로직 구현

3) JPA Converter 추가

  • ReservationNumberConverter
  • MoneyConverter
  • CancelReasonConverter
  • VO를 DB 컬럼과 안전하게 매핑할 수 있도록 변환 계층 분리

4) 테스트 및 Fixture 추가

  • ReservationTest로 상태 전이/멱등성/예외 케이스 검증
  • VO 단위 테스트 추가 (ReservationTimeSlot, Money, ReservationNumber, CancelReason, CancellationInfo)
  • Converter 단위 테스트 추가
  • ReservationFixture 추가로 테스트 데이터 생성 표준화

변경 파일 요약

  • 총 21개 파일 추가
  • 도메인/VO/Converter + 단위 테스트 일괄 포함

범위 외 (이번 PR 미포함)

  • Reservation API/Service/Repository 연결
  • 결제 모듈 연동
  • Saga 오케스트레이션
  • DB 마이그레이션/인덱스 설계

Summary by CodeRabbit

  • 새로운 기능

    • 예약 생성(보류), 확정, 취소, 만료의 완전한 예약 수명주기 지원 추가
    • 취소자 구분 및 취소 사유 기록, 결제 가능 여부·보유 기간 검사, 소유권 확인 기능 추가
    • 금액, 예약번호, 시간대 등 값객체 기반의 명확한 데이터 표현 지원
  • 테스트

    • 예약 도메인 및 값객체, 변환기 전반에 대한 단위 테스트 대폭 추가

@redblackblossom redblackblossom self-assigned this Feb 12, 2026
@redblackblossom redblackblossom added the ✨feature 기능 추가 label Feb 12, 2026
@redblackblossom redblackblossom linked an issue Feb 12, 2026 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Feb 12, 2026

📝 Walkthrough

Walkthrough

예약 도메인에 새로운 값 객체들(Money, ReservationNumber, ReservationTimeSlot, CancelReason, CancellationInfo), 열거형(CanceledBy, ReservationStatus), JPA 컨버터들 및 상태 머신을 포함한 JPA 엔티티 Reservation과 해당 단위 테스트 및 테스트 픽스처가 추가되었습니다.

Changes

Cohort / File(s) Summary
도메인 엔티티 및 상태 머신
reservation/src/main/java/net/catsnap/CatsnapReservation/reservation/domain/Reservation.java
새로운 JPA 엔티티 Reservation 추가 — hold/confirm/cancel/expire 상태 전환, 소유권 검사, 결제 가능성 검사, 감사 필드, 낙관적 락 등 포함.
도메인 열거형
reservation/src/main/java/.../domain/CanceledBy.java, reservation/src/main/java/.../domain/ReservationStatus.java
예약 취소자와 예약 상태를 나타내는 enum 2개 추가.
값 객체 (VOs)
reservation/src/main/java/.../domain/vo/Money.java, reservation/src/main/java/.../domain/vo/ReservationNumber.java, reservation/src/main/java/.../domain/vo/ReservationTimeSlot.java, reservation/src/main/java/.../domain/vo/CancelReason.java, reservation/src/main/java/.../domain/vo/CancellationInfo.java
불변 값 객체 5개 추가 — 검증 로직, 동치성, 문자열 표현 등 정의 및 일부는 @Embeddable로 사용됨.
JPA 컨버터
reservation/src/main/java/.../infrastructure/converter/CancelReasonConverter.java, .../MoneyConverter.java, .../ReservationNumberConverter.java
값 객체 ↔ DB 컬럼 간 변환을 위한 AttributeConverter 구현 3개 추가(자동 적용).
단위 테스트 — 도메인 및 VO
reservation/src/test/java/.../domain/ReservationTest.java, .../vo/CancelReasonTest.java, .../vo/CancellationInfoTest.java, .../vo/MoneyTest.java, .../vo/ReservationNumberTest.java, .../vo/ReservationTimeSlotTest.java
각 도메인 엔티티 및 값 객체에 대한 검증, 경계조건, 상태 전환, 연산 등을 검증하는 JUnit 5 테스트 추가.
단위 테스트 — 컨버터
reservation/src/test/java/.../infrastructure/converter/CancelReasonConverterTest.java, .../MoneyConverterTest.java, .../ReservationNumberConverterTest.java
컨버터의 양방향 변환, null 처리, 라운드트립 일관성 검증 테스트 추가.
테스트 픽스처
reservation/src/test/java/.../fixture/ReservationFixture.java
테스트용 기본 식별자, 기본 타임슬롯/금액/보류 만료 시각과 다양한 상태의 Reservation 인스턴스 생성용 팩토리 메서드 제공.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Reservation as Reservation Entity
    participant Database

    Client->>Reservation: hold(modelId,..., holdExpiresAt)
    activate Reservation
    Reservation->>Reservation: validate inputs\ngenerate ReservationNumber\nset status = PENDING
    deactivate Reservation
    Reservation-->>Client: Reservation (PENDING)

    Client->>Reservation: confirm(confirmedAt)
    activate Reservation
    Reservation->>Reservation: check status == PENDING\ncheck hold not expired\nset status = CONFIRMED\nset confirmedAt
    deactivate Reservation
    Reservation-->>Client: ✓ confirmed

    alt Cancel Flow
        Client->>Reservation: cancel(canceledBy, reason, canceledAt)
        activate Reservation
        Reservation->>Reservation: create CancellationInfo\nset status = CANCELED
        deactivate Reservation
        Reservation-->>Client: ✓ canceled
    else Expire Flow
        Client->>Reservation: expire(expiredAt)
        activate Reservation
        Reservation->>Reservation: check status == PENDING\nset status = EXPIRED
        deactivate Reservation
        Reservation-->>Client: ✓ expired
    end

    Reservation->>Database: persist state
    Database-->>Reservation: saved
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60분

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목 '[FEAT] 예약 도메인 개발 (#283)'은 전체 변경의 핵심인 예약 도메인 개발을 명확하게 요약하고 있습니다.
Description check ✅ Passed PR 설명이 배경, 변경 내용(4개 섹션), 파일 요약, 범위 외 사항 등을 포함하여 충분히 작성되었습니다.
Linked Issues check ✅ Passed PR의 모든 변경사항이 #283의 목표인 '예약 엔티티 정의 및 핵심 상태 전이·불변식 모델링'을 충족합니다.
Out of Scope Changes check ✅ Passed 21개 파일의 모든 변경사항이 예약 도메인 정의 범위 내에 있으며, 범위 외 사항(API/Service/Repository, 결제 연동 등)은 포함되지 않았습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#283/reservation_domain

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Feb 12, 2026

Test Results (reservation)

337 tests   337 ✅  7s ⏱️
 53 suites    0 💤
 53 files      0 ❌

Results for commit d851334.

♻️ This comment has been updated with latest results.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

MSA 전환 과정에서 결제/사가 연동 전에 **예약 코어 도메인(선점/확정/취소/만료)**의 상태 전이와 불변식을 엔티티/VO로 먼저 고정하기 위한 PR입니다.

Changes:

  • Reservation Aggregate Root 및 상태 전이(hold/confirm/cancel/expire) 도메인 로직 추가
  • 예약 관련 VO/Enum(ReservationNumber, ReservationTimeSlot, Money, CancelReason, CancellationInfo, ReservationStatus, CanceledBy) 추가
  • VO-DB 매핑을 위한 JPA Converter 및 단위 테스트/Fixture 추가

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
reservation/src/main/java/net/catsnap/CatsnapReservation/reservation/domain/Reservation.java 예약 Aggregate Root, 상태 전이/검증 로직 및 JPA 매핑 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/reservation/domain/ReservationStatus.java 예약 상태 Enum 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/reservation/domain/CanceledBy.java 취소 주체 Enum 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/reservation/domain/vo/ReservationNumber.java 외부 노출 예약번호(VO) 및 UUID 검증 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/reservation/domain/vo/ReservationTimeSlot.java 예약 시간대(VO, Embeddable) 및 불변식 검증 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/reservation/domain/vo/Money.java 금액(VO) 및 연산/검증 로직 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/reservation/domain/vo/CancelReason.java 취소 사유(VO) 및 길이/empty 판정 로직 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/reservation/domain/vo/CancellationInfo.java 취소 정보(VO, Embeddable) 및 검증 로직 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/reservation/infrastructure/converter/ReservationNumberConverter.java ReservationNumber ↔ String 변환 Converter 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/reservation/infrastructure/converter/MoneyConverter.java Money ↔ Long 변환 Converter 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/reservation/infrastructure/converter/CancelReasonConverter.java CancelReason ↔ String 변환 Converter 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/reservation/domain/ReservationTest.java Reservation 상태 전이/예외/멱등성 단위 테스트 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/reservation/fixture/ReservationFixture.java Reservation 테스트 Fixture 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/reservation/domain/vo/ReservationNumberTest.java ReservationNumber VO 단위 테스트 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/reservation/domain/vo/ReservationTimeSlotTest.java ReservationTimeSlot VO 단위 테스트 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/reservation/domain/vo/MoneyTest.java Money VO 단위 테스트 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/reservation/domain/vo/CancelReasonTest.java CancelReason VO 단위 테스트 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/reservation/domain/vo/CancellationInfoTest.java CancellationInfo VO 단위 테스트 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/reservation/infrastructure/converter/ReservationNumberConverterTest.java ReservationNumberConverter 단위 테스트 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/reservation/infrastructure/converter/MoneyConverterTest.java MoneyConverter 단위 테스트 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/reservation/infrastructure/converter/CancelReasonConverterTest.java CancelReasonConverter 단위 테스트 추가

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🤖 Fix all issues with AI agents
In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/reservation/domain/Reservation.java`:
- Around line 314-326: The two ownership-check methods (isOwnedByModel and
isOwnedByPhotographer) lack explicit null-argument validation; update both
methods to explicitly fail fast when the incoming ID is null (e.g., call
Objects.requireNonNull(modelId, "modelId must not be null") in isOwnedByModel
and Objects.requireNonNull(photographerId, "photographerId must not be null") in
isOwnedByPhotographer) before performing the existing equality check so a caller
bug is surfaced as an exception rather than silently returning false.
- Around line 353-368: Current equals/hashCode in Reservation uses only id,
causing two new (id==null) instances to be equal; update equals and hashCode in
the Reservation class so that if id is non-null you use id for identity,
otherwise fall back to the business natural key reservationNumber (UUID). Modify
the equals(Object) method and hashCode() accordingly: in equals check id
equality when both non-null, otherwise compare reservationNumber; in hashCode
include reservationNumber when id is null (or combine both deterministically).
This fixes incorrect equality for transient entities while preserving id-based
identity after persistence.
- Around line 143-163: The Reservation constructor currently validates IDs and
holdExpiresAt but misses null checks for timeSlot and amount; add fail-fast
validation for these fields (e.g., call a new private method like
validateTimeSlotAndAmount(timeSlot, amount) or inline checks) inside the private
Reservation(...) before assigning fields, and throw an appropriate runtime
exception (IllegalArgumentException or NullPointerException) with a clear
message if timeSlot or amount is null so the domain invariant is enforced in the
Reservation class.

In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/reservation/domain/vo/CancellationInfo.java`:
- Line 14: Remove domain-to-infrastructure imports and explicit `@Convert` usages
for converters that are declared with `@Converter`(autoApply = true);
specifically, in classes like CancellationInfo and Reservation remove imports of
CancelReasonConverter, MoneyConverter, ReservationNumberConverter and delete
their `@Convert`(converter = ...) annotations so JPA will apply the autoApply
converters automatically; keep explicit `@Convert` references only for converters
that are NOT autoApply (e.g., AvailableStartTimesConverter,
WeekdayRulesConverter).

In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/reservation/domain/vo/ReservationNumber.java`:
- Around line 32-37: The current ReservationNumber value validation uses
UUID.fromString(value) which can accept non-standard forms; update
ReservationNumber to validate against a strict UUID regex (e.g., add a
UUID_PATTERN constant) in the constructor/validation logic instead of relying
solely on UUID.fromString, and ensure generate() produced values still pass that
pattern; replace the try/catch block around UUID.fromString(value) in
ReservationNumber with a pattern match and throw
DomainException(DomainErrorCode.DOMAIN_CONSTRAINT_VIOLATION, "예약 번호는 UUID 형식이어야
합니다. 현재: " + value) when the regex does not match.

In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/reservation/infrastructure/converter/CancelReasonConverter.java`:
- Around line 13-21: The converter currently returns null for a null DB value
causing CancelReason(null) → DB null → null on read; update
CancelReasonConverter.convertToEntityAttribute to return a new
CancelReason(null) when dbData is null (keeping convertToDatabaseColumn as-is),
so round‑trip preserves the VO; reference the
CancelReasonConverter.convertToEntityAttribute method and the
CancelReason(String) constructor/getValue to locate where to change the mapping.

In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/reservation/domain/ReservationTest.java`:
- Around line 29-84: Add two missing null-validation tests for timeSlot and
amount in the ReservationTest Hold nested class: add one test that calls
Reservation.hold(..., null, DEFAULT_AMOUNT, ...) and asserts a DomainException
is thrown (message optional), and another that calls Reservation.hold(...,
DEFAULT_TIME_SLOT, null, ...) asserting DomainException; locate tests alongside
existing null_* tests in the Hold class that reference Reservation.hold and use
the same DEFAULT_* fixtures so they mirror null_모델ID... tests and validate
Reservation.hold's null checks for ReservationTimeSlot and Money.
- Around line 293-325: Add a boundary test to ReservationTest that asserts
isPayableAt returns false when called exactly at the hold expiry instant: create
a Reservation via ReservationFixture.createDefault() and assert
reservation.isPayableAt(DEFAULT_HOLD_EXPIRES_AT) isFalse(); place the test
alongside other IsPayableAt `@Test` methods so it verifies the exact-equality edge
case for isPayableAt against DEFAULT_HOLD_EXPIRES_AT.

In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/reservation/domain/vo/MoneyTest.java`:
- Around line 64-75: Add a unit test that verifies Money.add throws a
DomainException on overflow: create Money money1 = new Money(Long.MAX_VALUE) and
Money money2 = new Money(1L) and assert that calling money1.add(money2) results
in a DomainException; name the test something like add_오버플로우_시_예외가_발생한다 and rely
on Money.add and the constructor/validate() behavior to trigger the exception.

In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/reservation/domain/vo/ReservationNumberTest.java`:
- Around line 30-38: The test in ReservationNumberTest::generate로_새로운_예약번호를_생성한다
currently calls UUID.fromString(reservationNumber.getValue()) which will throw
an IllegalArgumentException on failure and produce a less clear failure message;
replace that raw call with an AssertJ assertion such as using assertThatCode(()
-> UUID.fromString(reservationNumber.getValue())).doesNotThrowAnyException() so
the assertion failure message is clearer and points to the UUID parsing; update
the assertion in the test after calling ReservationNumber.generate() and keep
the existing assertThat(reservationNumber.getValue()).isNotBlank() check.

In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/reservation/fixture/ReservationFixture.java`:
- Line 15: ReservationFixture is a pure utility class and should prevent
instantiation; add a private no-arg constructor (e.g., private
ReservationFixture() { throw new AssertionError("No instances."); }) inside the
ReservationFixture class to stop accidental construction, keeping existing
static methods and constants unchanged and optionally include an AssertionError
or comment for clarity.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/reservation/domain/Reservation.java`:
- Around line 353-363: The custom null-check method validateHoldExpiresAt
duplicates validateNotNull; replace calls to
validateHoldExpiresAt(holdExpiresAt) in the constructor (or wherever it's used)
with validateNotNull(holdExpiresAt, "홀드 만료 시각은 필수입니다.") and then remove the
redundant validateHoldExpiresAt method; keep validatePositive and
validateNotNull as the canonical validators and ensure any logic previously in
validateHoldExpiresAt is fully migrated before deletion.
- Around line 24-25: The Reservation domain currently imports MoneyConverter and
ReservationNumberConverter (and likely uses `@Convert` on fields) which makes the
domain depend on the infrastructure layer; instead annotate each converter class
(MoneyConverter and ReservationNumberConverter) with `@Converter`(autoApply =
true) in the infrastructure package so JPA will apply them automatically, then
remove the imports and any `@Convert`(...) annotations from the Reservation
aggregate root to eliminate the infra dependency from the domain.
- Around line 237-253: In cancel(), move null validation for canceledBy and
canceledAt to the start of the method (before any status logic) and throw the
same DomainException/DomainErrorCode used elsewhere when either is null so
callers fail fast instead of relying on CancellationInfo's constructor; keep the
existing status checks but add a brief comment that the final "if (status !=
PENDING && status != CONFIRMED)" is a defensive/forward-compatibility guard in
case new ReservationStatus values are added (so the unreachable-branch intent is
explicit).

@redblackblossom redblackblossom merged commit c668664 into main Feb 12, 2026
10 checks passed
@redblackblossom redblackblossom deleted the feat/#283/reservation_domain branch February 12, 2026 06:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨feature 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 예약 엔티티를 정의합니다.

1 participant