AgentSkillsCN

request-response

DTO包结构(请求与响应分离)以及JsonFormat注解的适用场景

SKILL.md
--- frontmatter
name: request-response
description: DTO package structure (request/response separation) and JsonFormat annotation use-site targets
triggers:
  - dto
  - request
  - response
  - json format
argument-hint: ""

Request & response conventions

Overview

This document defines conventions for API request/response DTOs: package structure, naming, and @JsonFormat annotation usage.

Key Principle: Separate API DTOs into dto/request/ and dto/response/ packages. Use correct @JsonFormat use-site targets.

Never return entities

IMPORTANT: Never return JPA entities directly as API responses. Always convert to a response DTO. Exposing entities leaks internal structure, bypasses timezone conversion, and couples the API contract to the database schema.

kotlin
// Bad: Returning entity directly
@GetMapping("/{id}")
fun getById(@PathVariable id: Long): ResponseEntity<ApiResource<Holiday>> =
    ApiResource.success(holidayService.findById(id))

// Good: Return response DTO
@GetMapping("/{id}")
fun getById(@PathVariable id: Long): ResponseEntity<ApiResource<HolidayDto>> =
    ApiResource.success(holidayFacade.findById(id))

Note: The full conversion flow is Entity → {Feature}Info (domain DTO) → {Feature}Dto (API response DTO). See skill: layer-architecture for details.

DTO package convention

IMPORTANT: API DTOs in bootstrap modules must be separated into dto/request/ and dto/response/ packages.

Package Structure

code
com.myrealtrip.{appname}/
├── api/
│   └── {Feature}Controller.kt
├── dto/
│   ├── request/
│   │   └── {Feature}ApiRequest.kt     # API request DTOs
│   └── response/
│       └── {Feature}ApiResponse.kt    # API response DTOs
└── facade/
    └── {Feature}Facade.kt

Naming Convention

TypeNamingPackageExample
API Request{Action}{Feature}ApiRequestdto/request/CreateHolidayApiRequest
API Response{Feature}Dto, {Feature}sResponsedto/response/HolidayDto, HolidaysResponse
Domain DTO{Feature}Infodomain dto/HolidayInfo
Domain RequestCreate{Feature}Requestdomain dto/CreateHolidayRequest

Correct Examples

kotlin
// dto/request/HolidayApiRequest.kt
package com.myrealtrip.commonapiapp.dto.request

data class CreateHolidayApiRequest(
    val holidayDate: LocalDate,
    val name: String,
)

// dto/response/HolidayApiResponse.kt
package com.myrealtrip.commonapiapp.dto.response

data class HolidayDto(
    val id: Long,
    val holidayDate: LocalDate,
    val name: String,
)

Incorrect Examples

kotlin
// Bad: request/response in the same file
data class HolidayDto(...)
data class CreateHolidayRequest(...)

// Bad: DTOs inside api/ package
package com.myrealtrip.commonapiapp.api.dto

JsonFormat annotation usage

Use @JsonFormat with appropriate use-site targets for JSON serialization/deserialization.

Annotation TargetUse CaseDescription
@param:JsonFormatRequest (Deserialization)Applied to constructor parameters for parsing incoming JSON
@get:JsonFormatResponse (Serialization)Applied to getter for formatting outgoing JSON
@field:JsonFormatBoth directionsApplied to field for both request and response

Correct Examples

kotlin
// Response only - use @get:JsonFormat
data class EventResponse(
    val id: Long,
    @get:JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
    val createdAt: LocalDateTime,
    @get:JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX'['VV']'")
    val scheduledAt: ZonedDateTime
)

// Request only - use @param:JsonFormat
data class CreateEventRequest(
    val name: String,
    @param:JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
    val startAt: LocalDateTime,
    @param:JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX'['VV']'")
    val scheduledAt: ZonedDateTime
)

// Both directions - use @field:JsonFormat
data class EventDto(
    val id: Long,
    @field:JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
    val createdAt: LocalDateTime,
    @field:JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX'['VV']'")
    val scheduledAt: ZonedDateTime
)

Incorrect Examples

kotlin
// Bad: Using @param for response serialization (won't work)
data class EventResponse(
    @param:JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
    val createdAt: LocalDateTime  // Output format not applied
)

// Bad: Using @get for request deserialization (won't work)
data class CreateEventRequest(
    @get:JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
    val startAt: LocalDateTime  // Input parsing not applied
)