| name | following-api-contract |
| description | Use when adding, changing, or reviewing HTTP API controllers, request and response DTOs, ApiResponse payloads, JSON field names, or response nesting in this repository |
API ์์ฒญยท์๋ต ๊ณ์ฝ
๊ณ์ธต๊ณผ ๋ณํ ํ๋ฆ
๊ธฐ์กด์ Request โ Command โ Result โ Response ๊ตฌ์กฐ์ ๊ฐ ํด๋์ค์ ์ญํ ์ ์ ์งํ๋ค.
Request, Response, ๋๋ฉ์ธ๋ณ Envelope๋ web ๋ชจ๋์ ๋๋ค.
Command, Result๋ core ๋ชจ๋์ ๋๋ค.
- Envelope๋ HTTP ์์ฒญ ๋ณธ๋ฌธ์ ๋ฐ๊ฑฐ๋ HTTP ์๋ต์ ๋ด๋ฆฌ๋ web ๊ฒฝ๊ณ์์๋ง ์ฌ์ฉํ๋ค.
- Command์ Result๋ฅผ Envelope๋ก ๊ฐ์ธ๊ฑฐ๋ web ๋ชจ๋๋ก ์ฎ๊ธฐ์ง ์๋๋ค.
- Envelope ์ ์ฉ๋ง์ ์ด์ ๋ก ๊ธฐ์กด Request, Command, Result, Response ํด๋์ค๋ฅผ ์ญ์ ํ๊ฑฐ๋ ๊ณต์ฉ DTO๋ก ๊ต์ฒดํ์ง ์๋๋ค.
- JSON ํ๋ ๊ณ์ฝ์ ๋ง์ถ๋ ๋ฐ ํ์ํ Request์ Response ํ๋ ๋ณ๊ฒฝ์ ํ์ฉํ๋ค.
์ ์ฒด ๋ณํ ํ๋ฆ์ ๋ค์๊ณผ ๊ฐ๋ค.
DomainEnvelope<WebRequest>
โ WebRequest.toCommand()
โ Core Command
โ UseCase
โ Core Result
โ WebResponse.from()/fromResult()
โ DomainEnvelope<WebResponse>
โ ApiResponse
์ฆ, ๊ฐ์ธ๋ ๋์์ HTTP ์
์ถ๋ ฅ์ Request์ Response๋ค. Result๋ฅผ Envelope์ ์ง์ ๋ฃ์ด ์๋ตํ์ง ์๋๋ค.
์์ฒญ
์์ฒญ ๋ณธ๋ฌธ์ ์ฃผ ๋๋ฉ์ธ์ ๋จ์ํ Envelope๋ก ๊ฐ์ธ๊ณ , ๊ทธ ์๋์ ์ค์ ์์ฒญ DTO์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ๋ก ๋๋ค.
{
"room": {
"title": "Trip",
"totalPhotoCount": 24
}
}
์ปจํธ๋กค๋ฌ๋ @RequestBody request: RoomEnvelope<CreateRoomRequest>์ฒ๋ผ Envelope๋ฅผ ๋ฐ๊ณ , request.room.toCommand(userId)๋ก ๋ณํํ๋ค. ์์ฒญ
DTO ์์ ๋๋ฉ์ธ ํ๋๋ฅผ ๋ค์ ๋ง๋ค์ง ์๋๋ค.
๋ค๋ฅธ ๋๋ฉ์ธ์ ์ฐธ์กฐํ๋ ํ๋๋ ํด๋น ๋๋ฉ์ธ ์ด๋ฆ์ ์ ์งํ๋ค.
{
"chat": {
"roomId": 1,
"photoId": 2,
"content": "์ข์ ์ฌ์ง์ด์์"
}
}
์๋ต
๋ชจ๋ ์๋ต์ ApiResponse<T>๋ก ๋ฐํํ๊ณ ๋๋ฉ์ธ๋ณ Envelope๋ฅผ data์ ๋ฃ๋๋ค. Envelope ์์๋ ์ด๋ฆ์ด Response๋ก ๋๋๋ web DTO๋ฅผ ๋ฃ๋๋ค.
{
"success": true,
"message": "OK",
"data": {
"room": {
"id": 1,
"title": "Trip"
}
}
}
์ค์ ๋ฐ์ดํฐ๊ฐ ๋ชฉ๋ก์ด์ด๋ ๊ฐ์ ๋จ์ํ Envelope๋ฅผ ์ฌ์ฉํ๋ค. data ๋ฐ๋ก ์๋์ ์ค์ ํ๋๋ฅผ ๋
ธ์ถํ๊ฑฐ๋ ๋๋ฉ์ธ์ ์๋ตํ์ง ์๋๋ค.
์ค์ฒฉ
๋๋ฉ์ธ ์๋์๋ ์ค์ ๋ฐ์ดํฐ ์ธ์ ์ค๊ฐ ๋ํผ๋ฅผ ์ถ๊ฐํ์ง ์๋๋ค.
{
"data": {
"room": {
"id": 1,
"status": "SHOOTING"
}
}
}
room.roomProjection, photo.photoDetail์ฒ๋ผ ํํ ๋ฐฉ์์ด๋ ์กฐํ ํํ๋ฅผ ๋ํ๋ด๋ ์ค๊ฐ ํ๋๋ ๋ง๋ค์ง ์๋๋ค.
๋ค๋ฅธ ๋๋ฉ์ธ์ ์์ ํ๋๊ฐ ํ์ํ๋ฉด ๋ณ๋ ๊ฐ์ฒด๋ก ๊ฐ์ธ์ง ์๊ณ ๋๋ฉ์ธ ์ด๋ฆ์ ๋ถ์ฌ ํํํํ๋ค.
{
"id": 1,
"content": "์ข์ ์ฌ์ง์ด์์",
"userName": "์ฐฐ๋ผ",
"userProfileImageUrl": "https://example.com/profile.jpg"
}
๋ชฉ๋ก
์ฃผ ๋๋ฉ์ธ ์์ฒด์ ๋ชฉ๋ก๋ data ์๋์ ๋จ์ํ ๋๋ฉ์ธ ์ด๋ฆ์ ๋ง๋ค๊ณ ๋ฐฐ์ด์ ๋ฐ๋ก ๋๋ค.
{
"data": {
"room": [
{
"id": 1,
"title": "Trip"
}
]
}
}
data.room: {}์ ๋จ๊ฑด, data.room: []๋ ๋ชฉ๋ก์ด๋ค. ๋ฐ์ดํฐ ํํ์ ๊ด๊ณ์์ด ๋๋ฉ์ธ ํค๋ ํญ์ ๋จ์ํ์ด๋ค. data.room.rooms, data.room.roomProjections
์ฒ๋ผ ๋๋ฉ์ธ๊ณผ ์ค์ ๋ฐ์ดํฐ ์ฌ์ด์ ์ค๊ฐ ํ๋๋ฅผ ์ถ๊ฐํ์ง ์๋๋ค.
์์ธยท๋ณตํฉ ๋ฐ์ดํฐ์ ํฌํจ๋ ๋ชฉ๋ก์ ์๋ฏธ๊ฐ ๋๋ฌ๋๋ ๋ณต์ํ ํ๋๋ช
์ ์ ์งํ๋ค. ์๋ฅผ ๋ค์ด ์ฌ์ง ์์ธ์ chats, ์ดฌ์ ์ ๋ณด์ cameraFilters๋ ์ ์งํ๋ค.
ํ๋๋ช
ํ๋๊ฐ ํ์ฌ ๋๋ฉ์ธ ์์ฒด๋ฅผ ๋ํ๋ด๋ฉด ํ๋๋ช
์์ ๋๋ฉ์ธ ์ด๋ฆ์ ์ ๊ฑฐํ๋ค.
room.roomId โ room.id
room[].roomId โ room[].id
room.roomStatus โ room.status
room.roomTitle โ room.title
photo.photoId โ photo.id
๋ชฉ๋ก์ ๋ด์ ๋จ์ํ ๋๋ฉ์ธ ์๋์ ๊ฐ ํญ๋ชฉ๋ ๊ฐ์ ๋๋ฉ์ธ์ผ๋ก ํ๋จํ๋ค. ๋ฐ๋ผ์ room ๋ฐฐ์ด์ ๊ฐ ํญ๋ชฉ์์ roomId๋ id๋ก ๋ฐ๊พผ๋ค.
ํ๋๊ฐ ๋ค๋ฅธ ๋๋ฉ์ธ์ ๋ํ๋ด๋ฉด ๊ตฌ๋ถ์ ์ํด ๋๋ฉ์ธ ์ด๋ฆ์ ์ ์งํ๋ค.
room.userId๋ ๊ทธ๋๋ก ๋๋ค.
chat.roomId๋ ๊ทธ๋๋ก ๋๋ค.
chat.photoId๋ ๊ทธ๋๋ก ๋๋ค.
chat.userName, chat.userProfileImageUrl์ ๊ทธ๋๋ก ๋๋ค.
ํ๋๋ช
์ ์ ํ๊ธฐ ์ ์ ํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ธ๋ ์ฃผ ๋๋ฉ์ธ๊ณผ ํ๋๊ฐ ์ค์ ๋ก ๊ฐ๋ฆฌํค๋ ๋๋ฉ์ธ์ ๋จผ์ ๊ตฌ๋ถํ๋ค.
Envelope์ DTO
- ๊ฐ ๋๋ฉ์ธ์ generic Envelope๋ฅผ ๋ง๋ ๋ค.
- Envelope๋ ๋จ์ํ ๋๋ฉ์ธ ํ๋๋ฅผ ๊ฐ๋๋ค.
- ๋จ๊ฑด๊ณผ ๋ชฉ๋ก์ ๊ฐ์ Envelope๋ฅผ ์ฌ์ฉํ๋ค.
- ์์ฒญ๊ณผ ์๋ต์ ๋๋ฉ์ธ wrapping์ ๋ชจ๋ Envelope๋ก ์ฒ๋ฆฌํ๋ค.
- ์์ฒญ Envelope ์์๋ ์ด๋ฆ์ด
Request๋ก ๋๋๋ ์์ฒญ DTO๋ฅผ ๋ฃ๋๋ค.
- ์๋ต Envelope ์์๋ ์ด๋ฆ์ด
Response๋ก ๋๋๋ ์๋ต DTO ๋๋ ๊ทธ ๋ชฉ๋ก์ ๋ฃ๋๋ค.
- core์ Result๋ ๋๋ฉ์ธ ๊ฐ์ฒด๋ฅผ ์๋ต Envelope์ ์ง์ ๋ฃ์ง ์๋๋ค. Envelope์ ์ง์ ๊ฐ์ web Response์ฌ์ผ ํ๋ค.
- ์๋ต DTO๋ ๊ธฐ์กด ๋ณํ ๋ฐฉ์์ธ
from() ๋๋ fromResult()์์ Result๋ ๋๋ฉ์ธ ๊ฐ์ฒด๋ฅผ ๋ณํํ๋ค.
- ์๋ต DTO์๋ ์ค์ ์๋ต ํ๋๋ง ๋๊ณ ๋๋ฉ์ธ wrapper ํ๋๋ฅผ ๋ค์ ๋ง๋ค์ง ์๋๋ค.
- ๊ธฐ์กด Response๊ฐ ์ค์ฒฉ core ํ์
์ ํ๋๋ก ์ฌ์ฉํ๊ณ ์์๋ค๋ฉด JSON ๊ณ์ฝ์ ๋ณ๊ฒฝ์ด ํ์ํ์ง ์์ ํ ์ด๋ฅผ ๋ณต์ ํ ์ค์ฒฉ Response DTO๋ฅผ ์๋ก ๋ง๋ค์ง ์๋๋ค.
- Envelope๊ฐ ๊ธฐ์กด Response์ ๋๋ฉ์ธ wrapper ์ญํ ์ ๋์ ํ๋๋ผ๋ ๊ธฐ์กด Response ํด๋์ค ์์ฒด๋ ์ ์งํ๊ณ ์ค์ ๋ฐ์ดํฐ ํํ ์ญํ ๋ก ์กฐ์ ํ๋ค.
- Envelope๋ ๋๋ฉ์ธ wrapping ์ญํ ์ด๋ฏ๋ก
Response๋ก ์ด๋ฆ์ ๋ฐ๊พธ์ง ์๋๋ค.
- ๊ธฐ์กด Request, Command, Result, Response ํด๋์ค ์ด๋ฆ๊ณผ ๊ณ์ธต์ ์ ์งํ๋ค.
- ์์ฒญ ๋ณํ์ Envelope ์์ ์์ฒญ DTO๊ฐ ์ ๊ณตํ๋
toCommand()์์ ์ฒ๋ฆฌํ๋ค.
data class RoomEnvelope<T : Any>(val room: T)
data class CreateRoomResponse(val invitationCode: String) {
companion object {
fun fromResult(result: CreateRoomResult) = CreateRoomResponse(
invitationCode = result.invitationCode
)
}
}
fun createRoom(
@AuthUserId userId: Long,
@RequestBody request: RoomEnvelope<CreateRoomRequest>
): ApiResponse<RoomEnvelope<CreateRoomResponse>> {
val result = createRoomUsecase.createRoom(request.room.toCommand(userId))
return ApiResponse.ok(RoomEnvelope(CreateRoomResponse.fromResult(result)))
}
val result: ListRoomsResult = listRoomsUsecase.listRooms(command)
val response = ApiResponse.ok(RoomEnvelope(result.roomProjections.map(ListRoomsResponse::fromResult)))
๋น ์ฑ๊ณต ์๋ต
๋ฐํํ ์ค์ ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด ApiResponse.ok(Unit) ๋์ ApiResponse.empty()๋ฅผ ์ฌ์ฉํ๋ค.
์ ๊ฒ
๊ตฌํ์ ๋ง์น๋ฉด ๋ค์์ ํ์ธํ๋ค.
- ์์ฒญ ์ต์์๊ฐ ์ฃผ ๋๋ฉ์ธ์ผ๋ก ๊ฐ์ธ์ก๋๊ฐ?
- ์์ฒญ๊ณผ ์๋ต์ ๋๋ฉ์ธ wrapping์ ๋๋ฉ์ธ๋ณ Envelope๋ฅผ ์ฌ์ฉํ๋๊ฐ?
- Envelope๊ฐ web ๊ฒฝ๊ณ์๋ง ์๊ณ core Command์ Result์๋ ๋ค์ด๊ฐ์ง ์์๋๊ฐ?
- ๊ธฐ์กด Request โ Command โ Result โ Response ๋ณํ ๊ตฌ์กฐ์ ํด๋์ค ์ญํ ์ ์ ์งํ๋๊ฐ?
- ์๋ต ๋ฐ์ดํฐ๊ฐ
ApiResponse.data ์๋์ ์ฃผ ๋๋ฉ์ธ ์์ ์๋๊ฐ?
- ๋จ๊ฑด๊ณผ ๋ชฉ๋ก ๋ชจ๋ ๋จ์ํ ๋๋ฉ์ธ Envelope๋ฅผ ์ฌ์ฉํ๋๊ฐ?
- ์๋ต Envelope ์์ ํด๋์ค ์ด๋ฆ์ด
Response๋ก ๋๋๋๊ฐ?
- core Result๋ ๋๋ฉ์ธ ๊ฐ์ฒด๋ฅผ ์ง์ ์๋ตํ๊ณ ์์ง ์์๊ฐ?
- ๊ธฐ์กด ์ค์ฒฉ ํ์
์ ๋ณต์ ํ ๋ถํ์ํ Response DTO๋ฅผ ์ถ๊ฐํ์ง ์์๋๊ฐ?
- ๋๋ฉ์ธ ์๋์ ๋ถํ์ํ ์ค๊ฐ ๋ํผ๊ฐ ์๋๊ฐ?
- ํ์ฌ ๋๋ฉ์ธ์ ๋ฐ๋ณตํ๋ ํ๋๋ช
์ ์ ๊ฑฐํ๋๊ฐ?
- ๋ค๋ฅธ ๋๋ฉ์ธ์ ๋ํ๋ด๋ ํ๋๋ช
์ ์ ์งํ๋๊ฐ?
- ์ฃผ ๋๋ฉ์ธ ๋ชฉ๋ก์ ๋จ์ํ ๋๋ฉ์ธ ๋ฐ๋ก ์๋ ๋ฐฐ์ด์ธ๊ฐ?
- ๊ธฐ์กด ์ปจํธ๋กค๋ฌ ํ
์คํธ์ JSON ๊ฒฝ๋ก๋ฅผ ๋ณ๊ฒฝ๋ ๊ณ์ฝ์ ๋ง์ท๋๊ฐ?