Skip to content

공통 API 응답 포맷 추가 (ApiResponse, CursorSliceResponse) #7

Description

@xeoxxn

어떤 기능인가요?

api:common-api 모듈에 소스 파일이 하나도 없어 모든 API가 공유할 응답 래퍼가 없다. 공통 응답 포맷 ApiResponse<T>와 커서 기반 목록 조회 응답 CursorSliceResponse<T>를 추가한다.

기존 ErrorCode/BusinessException(core:common)을 그대로 받아 성공·실패 응답을 만든다. ErrorCode 체계는 새로 만들지 않는다.

완료 기준 — 세 타입이 존재하고 CursorSliceResponse.from(CursorSliceResult)로 조회 결과를 응답으로 변환할 수 있다. :api:common-api:compileJava가 통과한다.

📝 작업 상세 내용

  • ApiResponse<T> 추가 — api:common-api, kr.ac.kookmin.stream
    • final class + @Getter + @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
    • 필드 boolean success / String code / String message / T data (전부 final)
    • 정적 팩토리 5개 — success(T), success(), error(BusinessException), error(ErrorCode), error(String, String)
    • @Accessors(fluent = true)는 붙이지 않는다 (Jackson getter 규약에서 벗어나 프로퍼티가 인식되지 않음)
  • CursorSliceResult<T> 추가 — core:common, record(List<T> content, boolean hasNext, Long nextCursor)
  • CursorSliceResponse<T> 추가 — api:common-api, record + 정적 팩토리 from(CursorSliceResult<T>)
  • ./gradlew :api:common-api:compileJava 통과 확인
  • ./gradlew :bootstrap:test --tests "*ModularityTests" 통과 확인

📁 참고 자료 (선택)

스코프 밖

  • GlobalExceptionHandler — 만들지 않는다. ApiResponse.error(...)를 호출하는 주체가 이것이라, 이 이슈만으로는 401/403 및 모든 예외가 여전히 공통 포맷으로 나가지 않는다. ApiResponse는 "호출될 준비만 된" 상태가 된다 → 후속 작업 필요
  • PageResult<T> / PageResponse<T> — 오프셋 페이징 요구가 생길 때
  • springdoc @Schema / @ApiErrorCode — 저장소에 springdoc 의존성이 없어 애노테이션만 붙일 수 없다. 의존성 도입 작업에서 함께

컨벤션 근거

  • coding-style.md 2-2절 — ApiResponse 클래스 본문(private 생성자 + 정적 팩토리)
  • coding-style.md 2-4절 — 커서 응답 필드는 content/hasNext/nextCursor, {X}Result{X}Response
  • coding-style.md 2-3·2-10·2-11절 — from(...) 변환, 정적 팩토리 네이밍, Lombok 범위
  • error-handling.md 5절 — error(...) 팩토리 시그니처 3종
  • architecture.md 101~119줄 — 패키지 배치. common-api는 하위 패키지 없이 {basePackage}

참고 구현 (Kotlin)

참고 구현 기준으로 확정한 항목

동치미 구현을 기준으로 정리했다. 둘 다 "안 한다" 쪽이다.

  1. data가 null이어도 JSON에서 생략하지 않는다@JsonInclude를 붙이지 않는다. 동치미 ApiResponse.kt에 Jackson 애노테이션이 없어 "data": null이 그대로 직렬화된다. 키가 항상 존재하는 편이 클라이언트 파싱에 예측 가능하다. 애노테이션을 안 쓰므로 Jackson import도 필요 없다.
  2. CursorSliceResponse.content에 방어적 복사를 넣지 않는다 — 동치미 보조 생성자가 cursorSliceResult.contenttoList() 없이 그대로 넘긴다. List.copyOf(...)contentnull일 때 NPE가 되고 컨벤션에 규정이 없다.

참고 구현과 다르게 가는 건 @Schema 하나뿐이며, 이는 선택이 아니라 springdoc 의존성이 없어서 생기는 제약이다.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions