생성되는 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 |
| 없는 행 수정 | EntityNotFoundException | 404 |
| 없는 행을 가리키는 다른 애그리거트의 키 (DDD) | reference.<Entity>.<relation> | 404 Member 99 does not exist |
지금 상태로는 할 수 없는 명령·삭제 (states:) | state.... | 409 |
| 다른 불변식 위반 | InvariantViolationException | 422 |
| 손으로 쓴 규칙이 거절 | 아래 런타임 예외 | 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 이 아님 | 400 | unreadable |
타입이 받지 않는 값 - 숫자 자리의 글자, 없는 enum 코드, 음수 quantity | 400 | invalid, fieldErrors[].code 는 typeMismatch (lines[0].quantity: a quantity cannot be negative: -1) |
| 필수 쿼리 파라미터 없음 | 400 | invalid, NotNull |
| 없는 경로 | 404 | not-found |
그 경로에 없는 메서드 (@editable 이 아닌 루트에 PUT) | 405 (Allow 헤더 그대로) | method-not-allowed |
줄 수 없는 응답 형식 (Accept: image/png) | 406 | not-acceptable |
읽을 수 없는 본문 형식 (Content-Type: text/plain) | 415 | unsupported-media-type |
브라우저가 페이지를 달라고 하면(Accept: text/html) 404·405 는 Spring Boot 의 오류 페이지가 그대로 나옵니다.
spring.mvc.problemdetails.enabled=true 는 켜지 마세요 - Spring Boot 의 처리기가 필드 검증 응답까지 가져갑니다.
Spring Framework 6.1(Spring Boot 3.2) 이상이 필요합니다.
코드
| 코드 |
|---|