| name | api-patterns |
| description | Use when writing or modifying Spring WebMVC controllers and REST endpoints. Covers the `DpmApiResponse<T>` response wrapper, `DpmException` + `ErrorCode` for errors, `@Operation`/`@Tag` Swagger docs (mandatory on every endpoint), request DTO validation, and HTTP status conventions. Trigger on any file ending in `Controller.kt` or when adding a new API route. |
API Patterns
Response Wrapper
모든 엔드포인트는 DpmApiResponse<T>로 응답을 감싸야 한다.
data class DpmApiResponse<T>(
val data: T? = null,
val error: ErrorResponse? = null,
)
fun someEndpoint(): DpmApiResponse<SomeResponse> {
val result = someService(params)
return DpmApiResponse.ok(result)
}
fun deleteEndpoint(): DpmApiResponse<Unit> {
someService.delete(id)
return DpmApiResponse.ok()
}
컨트롤러 구조
@RestController
@RequestMapping("${ContextConstants.API_VERSION_V1}/meetings")
@Tag(name = "Meeting", description = "미팅 관련 API")
class MeetingController(
private val createMeetingService: CreateMeetingService,
private val getMeetingService: GetMeetingService,
) {
@PostMapping
@Operation(summary = "미팅 생성", description = "새로운 미팅을 생성합니다.")
@ApiResponses(
ApiResponse(responseCode = "200", description = "성공"),
ApiResponse(responseCode = "404", description = "사용자 없음 - USER_NOT_FOUND"),
)
suspend fun createMeeting(
@UserId userId: Long,
@Valid @RequestBody request: CreateMeetingRequest,
): DpmApiResponse<MeetingResponse> {
return DpmApiResponse.ok(createMeetingService(userId, request))
}
}
사용자 인증 추출
@AuthenticationPrincipal을 직접 사용하지 말 것. 항상 커스텀 @UserId 어노테이션을 사용한다.
suspend fun someEndpoint(@UserId userId: Long): DpmApiResponse<SomeResponse>
suspend fun someEndpoint(@UserId userId: Long?): DpmApiResponse<SomeResponse>
suspend fun someEndpoint(@MeetingId meetingId: Long): DpmApiResponse<SomeResponse>
예외 처리
RuntimeException이나 일반 예외를 던지지 말 것. 반드시 도메인별 DpmException 서브클래스를 사용한다.
class MeetingException(
errorCode: ErrorCode,
detail: Map<String, Any?>? = null,
) : DpmException(errorCode, detail)
throw MeetingException(ErrorCode.MEETING_NOT_FOUND)
throw MeetingException(ErrorCode.INVALID_REQUEST, mapOf("field" to "name", "value" to name))
요청 유효성 검사
data class CreateMeetingRequest(
@field:NotBlank(message = "미팅 이름은 필수입니다")
val name: String,
@field:Size(max = 50)
val description: String?,
)
suspend fun create(@Valid @RequestBody request: CreateMeetingRequest)
Command 객체
컨트롤러 → 서비스 간 파라미터가 많을 경우 Command 객체로 묶는다.
data class CreateMeetingCommand(
val userId: Long,
val name: String,
val locationId: Long,
)
val command = CreateMeetingCommand(userId, request.name, request.locationId)
createMeetingService(command)
Swagger 문서화 체크리스트
새 엔드포인트 추가 시: