# 서비스 javadoc 이 네 연산을 조건부라 부른다
19:/**
20: * JPA-backed {@link FileQuotaService}.
21: *
22: * <p>Reservation, extension, commit, and release are conditional statements, so a reservation that
23: * already expired or was released can never be extended or committed.
24: */
25:@Repository
# 저장소 인터페이스 javadoc 은 한 걸음 더 나간다
13:/**
14: * Conditional quota reservation statements.
15: *
16: * <p>Extend, commit, and release all require the reservation to still be {@code RESERVED} at the
17: * expected version, so a reservation reclaimed by expiry cannot be resurrected.
18: */
#   그런데 세 질의 시그니처에 version 파라미터: 0

# 연장 질의의 조건
22:  @Query(
23:      """
24:      update QuotaReservationEntity q
25:         set q.reservedBytes = q.reservedBytes + :additionalBytes,
26:             q.version = q.version + 1,
27:             q.updatedAt = :now
28:       where q.reservationId = :reservationId
29:         and q.status = 'RESERVED'
30:         and q.expiresAt > :now
31:      """)
32:  int extend(
# 확정 질의의 조건
38:  @Query(
39:      """
40:      update QuotaReservationEntity q
41:         set q.status = 'COMMITTED',
42:             q.committedBytes = :actualBytes,
43:             q.reservedBytes = 0,
44:             q.version = q.version + 1,
45:             q.updatedAt = :now
46:       where q.reservationId = :reservationId
47:         and q.status = 'RESERVED'
48:      """)
49:  int commit(
50:      @Param("reservationId") UUID reservationId,

# 만료는 상태로 남지 않는다
application-core/src/main/java/dev/caskeleton/application/fileserver/api/metadata/QuotaReservationStatus.java:13:  EXPIRED
#   STALE_QUOTA_RESERVATION 을 enqueue 하는 main 코드: 0

# 이 포트의 네 메서드를 부르는 프로덕션 코드
#   quotaService.reserve() : 1
#   quotaService.extend() : 0
#   quotaService.commit() : 0
#   quotaService.release() : 1
application-core/src/main/java/dev/caskeleton/application/fileserver/upload/DefaultUploadApplicationService.java:128:                    quotaService.reserve(scope, reservationBytes, uploadPolicy.reservationTtl());
application-core/src/main/java/dev/caskeleton/application/fileserver/upload/DefaultUploadApplicationService.java:157:              quotaService.release(created.reservation());

# 프로덕션 확정이 실제로 지나는 곳
application-core/src/main/java/dev/caskeleton/application/fileserver/upload/DefaultFinalizeUploadService.java:315:                quotaGateway.commit(session, published.size());
#   그 게이트웨이가 살아 있는 예약을 고르는 조회의 조건
90:  /** Live reservations for a scope, oldest first. */
91:  @Query(
92:      """
93:      select q from QuotaReservationEntity q
94:       where q.scopeType = :scopeType
95:         and q.scopeValue = :scopeValue
96:         and q.status = 'RESERVED'
97:         and q.expiresAt > :now
98:       order by q.createdAt asc
99:      """)
100:  List<QuotaReservationEntity> findActiveReservations(
#   살아 있는 예약이 없을 때
103:  private void recordCommittedWithoutReservation(QuotaScope scope, long actualBytes, Instant now) {
104-    if (actualBytes == 0) {
105-      return;
106-    }
107-    QuotaReservationEntity settled =
108-        new QuotaReservationEntity(
109-            UUID.randomUUID(), scope.type(), scope.value(), actualBytes, now, "RESERVED", now);
110-    reservations.save(settled);
111-    reservations.commit(settled.getReservationId(), actualBytes, now);
112-  }
113-}
#   그 행을 만드는 생성자의 인자 순서 — 다섯째가 expiresAt
58:  public QuotaReservationEntity(
59-      UUID reservationId,
60-      String scopeType,
61-      String scopeValue,
62-      long reservedBytes,
63-      Instant expiresAt,
64-      String status,
65-      Instant createdAt) {
66-    this.reservationId = reservationId;
67-    this.scopeType = scopeType;
