본문으로 건너뛰기
ElastiCORE 3.0 은 릴리스 후보(RC)입니다: Maven Central 의 3.0.261022-rc1. 2.2 라인 프로젝트는 2.2 문서를 보세요.
버전: 3.0

생성되는 API 와 오류 응답

@expose 엔티티의 API​

기본 경로는 /api/<도메인>/<엔티티> - 엔티티 이름의 첫 글자를 소문자로(PurchaseOrder 는 /api/shop/purchaseOrder). env.yml 의 apiPath 가 /api 를 정합니다.

요청하는 일
POST / 필드와 함께생성. 키가 채워진 행을 답함
GET /{key}한 건 조회. 없으면 404
PUT / 키는 본문에수정 (@editable 루트나 필드)
DELETE /{key}삭제. true 를 답함
POST /search {필드: 값, "pageNumber": 0, "pageSize": 20, "sortCode": "title+"}페이지: content, totalElements, totalPages, number
PUT /batch 목록한 트랜잭션에서 여러 건 수정 - 행마다 update(dto) 를 거침
POST /delete 키 목록여러 건 삭제. true/false 목록
GET /schema/info범용 화면용 필드 설명
POST /{key}/<명령>생명주기 이동이나 업무 명령 (DDD 번들)
POST /signin, POST /signout, GET /me@signin 이 있을 때

목록용 GET 은 없습니다. 컨트롤러는 모든 동작을 springdoc 문서로 남깁니다(/swagger-ui.html, /v3/api-docs).

@restExclude(...)​

서비스·컨트롤러·유스케이스·TypeScript 클라이언트에서 메서드를 뺍니다. 이름은 서비스의 save, update, batchUpdate, delete(목록), deleteById, updateBySpecification 과 컨트롤러의 create, read, search, update, batchUpdate, deleteBatch(POST /delete), deleteById, schemaInfo 입니다. v3 에서는 뺀 메서드를 부르는 것도 함께 빠져 컴파일이 깨지지 않습니다: update 는 batchUpdate 를, deleteById 는 delete 와 deleteBatch 를, delete 는 deleteBatch 를, save 는 create 를 데려갑니다. Kotlin 서비스는 메서드를 그대로 두고 컨트롤러와 유스케이스만 따릅니다. 2.x 호환 출력은 적힌 대로만 뺍니다.

v3 쓰기 경로가 확인하는 것​

jpa-springboot-v3, ddd-springboot, hexagonal-springboot 와 Kotlin 판의 코어 서비스는 쓰기 전에 확인합니다(2.x 호환 jpa-springboot 는 바뀌지 않음):

경우동작응답
이미 있는 키로 생성거절, 덮어쓰지 않음409
보낸 version 이 행의 것과 다른 수정거절 (낙관적 잠금)409
수정 값이 제약 위반 (@Size ...)거절 - 보낸 필드만 검사400
없는 행 수정EntityNotFoundException404
없는 행을 가리키는 다른 애그리거트의 키 (DDD)reference.<Entity>.<relation>404 Member 99 does not exist
지금 상태로는 할 수 없는 명령·삭제 (states:)state....409
다른 불변식 위반InvariantViolationException422
손으로 쓴 규칙이 거절아래 런타임 예외400 / 401 / 403 / 404 / 409 / 502

DDD 번들의 매퍼는 @jpa:Version 과 Spring Data 감사 필드를 요청에서 쓰지 않습니다: 응답에는 나가므로 클라이언트가 버전을 되보내지만, 요청으로 감사 기록을 위조할 수는 없습니다.

오류 응답: application/problem+json​

모든 v3 번들은 노출된 엔티티가 있는 도메인마다 컨트롤러 패키지에 XxxApiErrors(@RestControllerAdvice, RFC 9457 ProblemDetail)를 하나 생성합니다 - 한 번만 생성이라 응답 형식은 개발자 것입니다. 이 파일은 런타임의 RequestShapeErrors 도 가져와, Spring MVC 가 컨트롤러를 부르기 전에 거절한 요청을 애플리케이션 전체에서 같은 모양으로 답합니다.

HTTP/1.1 409
Content-Type: application/problem+json

{ "title": "Not allowed in this state",
"detail": "PurchaseOrder cannot cancel when status is SHIPPED; it must be [PLACED]",
"code": "state.PurchaseOrder.status.cancel", "status": 409 }

필드 검증의 400 에는 errors("필드: 메시지")와 fieldErrors 가 있습니다:

{ "status": 400, "code": "invalid",
"fieldErrors": [{ "field": "lines", "code": "Size", "message": "the cart is empty" }] }

컨트롤러에 닿기 전의 거절 (RequestShapeErrors)​

요청상태code
본문이 없거나 JSON 이 아님400unreadable
타입이 받지 않는 값 - 숫자 자리의 글자, 없는 enum 코드, 음수 quantity400invalid, fieldErrors[].code 는 typeMismatch (lines[0].quantity: a quantity cannot be negative: -1)
필수 쿼리 파라미터 없음400invalid, NotNull
없는 경로404not-found
그 경로에 없는 메서드 (@editable 이 아닌 루트에 PUT)405 (Allow 헤더 그대로)method-not-allowed
줄 수 없는 응답 형식 (Accept: image/png)406not-acceptable
읽을 수 없는 본문 형식 (Content-Type: text/plain)415unsupported-media-type

브라우저가 페이지를 달라고 하면(Accept: text/html) 404·405 는 Spring Boot 의 오류 페이지가 그대로 나옵니다. spring.mvc.problemdetails.enabled=true 는 켜지 마세요 - Spring Boot 의 처리기가 필드 검증 응답까지 가져갑니다. Spring Framework 6.1(Spring Boot 3.2) 이상이 필요합니다.

코드​

코드언제
not-found, already-exists, stale, required, unique, referenced, conflict, invalid, refused, state, not-signed-in, forbidden, bad-request, no-such-property모든 도메인 공통
state.<Entity>.<field>.<command>시작 상태가 아닌데 온 생명주기 명령 (409)
invariant.<Entity>.<field>.min / .max / .minsize / .maxsize필드 범위 불변식
invariant.<Entity>.<조건>@invariant (items.size > 0 이면 items-size-gt-0)
reference.<Entity>.<relation>가리킨 행이 없음 (404)
upstream다른 시스템 호출 실패 (PortCallException, 502)
unreadable, method-not-allowed, unsupported-media-type, not-acceptable컨트롤러 전의 거절

TypeScript 클라이언트의 ApiError 가 code 와 fieldErrors 를 읽습니다(TypeScript).

손으로 쓴 거절​

예외 (io.elasticore.springboot3.)상태코드 없이 던지면 code
validation.BadRequestException(code, message, args...)400bad-request
security.NotSignedInException(code, message, args...)401not-signed-in
security.ForbiddenException(code, message, args...)403forbidden
validation.NotFoundException(code, message, args...)404not-found
validation.ConflictException(code, message, args...)409conflict
http.PortCallException(code, message, args...)502upstream
validation.InvariantViolationException(code, message, args...)state. 409, reference. 404, 그 밖 422

메시지가 detail 이 되고, 메시지 코드를 주면 요청 언어로 번역됩니다(메시지와 언어). 코드 없이 new ConflictException("...") 으로 던져도 됩니다.

throw new ConflictException("loan.no-copy-left", "No copy of {0} is left to lend", isbn);
HTTP/1.1 409
{ "title": "Conflicts with stored data", "detail": "No copy of 978-0-00 is left to lend",
"code": "loan.no-copy-left", "status": 409 }
손댄 XxxApiErrors

한 번만 생성되는 파일입니다. 손대지 않았으면 바뀔 때마다 다시 생성되고(REGENERATED(owned, never edited)), 손댔으면 그대로입니다. 3.0.261022-rc1 부터 생명주기 거절은 409, 없는 참조는 404 입니다(전에는 422 와 외래 키의 409). 손댄 파일은 예전처럼 답하므로 refused(...) 를 새로 생성된 것처럼 고치고(state. 409, reference. 404), @Import(io.elasticore.springboot3.validation.RequestShapeErrors.class) 를 붙이거나, 파일을 지우고 다시 빌드하세요. 생성기와 런타임 라이브러리는 같은 버전으로 올립니다.