# 서비스 javadoc 이 네 연산을 조건부라 부른다 19:/** 20: * JPA-backed {@link FileQuotaService}. 21: * 22: *

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: *

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 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;