Home > Backend Development > πŸ“š[Backend Development] CRUD 각 κΈ°λŠ₯별 Request/Response Body 섀계 μ™„λ²½ κ°€μ΄λ“œ

πŸ“š[Backend Development] CRUD 각 κΈ°λŠ₯별 Request/Response Body 섀계 μ™„λ²½ κ°€μ΄λ“œ
Backend Ddevelopment HTTP RESTful API API Architecture CRUD

🎯 CRUD API Request/Response Body 섀계 μ™„λ²½ κ°€μ΄λ“œ

πŸ€” μ™œ 이 κ°€μ΄λ“œλ₯Ό λ§Œλ“€μ—ˆλ‚˜?

Java Spring λ°±μ—”λ“œ ν”„λ‘œμ νŠΈμ—μ„œ API λͺ…μ„Έμ„œλ₯Ό μž‘μ„±ν•˜λŠ” κ³Όμ •μ—μ„œ, CRUD의 Request/Response Bodyλ₯Ό μ–΄λ–»κ²Œ 섀계해야 ν•˜λŠ”μ§€ λͺ…ν™•ν•œ 원칙과 νŒ¨ν„΄μ„ μ΄ν•΄ν•˜μ§€ λͺ»ν•΄ μ •λ¦¬ν•œ μ‹€μ „ κ°€μ΄λ“œμž…λ‹ˆλ‹€.

🎯 ν•™μŠ΅ λͺ©ν‘œ

RESTful API μ„€κ³„μ˜ 핡심인 CRUD 각 κΈ°λŠ₯별 Request/Response Body μž‘μ„± 원칙과 νŒ¨ν„΄μ„ μ™„λ²½νžˆ λ§ˆμŠ€ν„°ν•˜μž!


πŸ“‹ λͺ©μ°¨

  1. RESTful API Body μ„€κ³„μ˜ 핡심 원칙
  2. CRUD별 Request/Response Body νŒ¨ν„΄
  3. Spring Boot μ‹€μ „ 적용 μ˜ˆμ‹œ
  4. API λͺ…μ„Έμ„œ μž‘μ„± 체크리슀트

🎨 RESTful API Body μ„€κ³„μ˜ 핡심 원칙

πŸ’‘ λŒ€μ›μΉ™: β€œμ΅œμ†Œν•œμ˜ μ •λ³΄λ‘œ λͺ…ν™•ν•˜κ²Œ μ†Œν†΅ν•œλ‹€β€

RESTful API의 Body μ„€κ³„λŠ” 각 HTTP Method의 본래 λͺ©μ μ— 맞좰 ν•„μš”ν•œ μ •λ³΄λ§Œ μ£Όκ³ λ°›λŠ” 것이 ν•΅μ‹¬μž…λ‹ˆλ‹€.

πŸ“Š HTTP Method별 Body μ‚¬μš© 원칙

HTTP Method Request Body Response Body 핡심 원칙
POST πŸ“ βœ… ν•„μˆ˜ βœ… ν•„μˆ˜ 생성에 ν•„μš”ν•œ λͺ¨λ“  정보 β†’ μ™„μ „ν•œ 생성 κ²°κ³Ό
GET πŸ” ❌ μ‚¬μš© μ•ˆν•¨ βœ… ν•„μˆ˜ URL νŒŒλΌλ―Έν„°λ‘œ 쑰건 β†’ 쑰회 κ²°κ³Ό λ°˜ν™˜
PATCH/PUT ✏️ βœ… ν•„μˆ˜ βœ… ν•„μˆ˜ λ³€κ²½ν•  μ •λ³΄λ§Œ β†’ μ™„μ „ν•œ μˆ˜μ • κ²°κ³Ό
DELETE πŸ—‘οΈ ❌ μ‚¬μš© μ•ˆν•¨ ❌ λΉ„μ›Œλ‘  URL둜 식별 β†’ 204 μƒνƒœ μ½”λ“œλ‘œ 성곡 ν‘œμ‹œ

πŸ”„ CRUD별 Request/Response Body νŒ¨ν„΄

πŸ“ CREATE (POST) - μƒˆλ‘œμš΄ λ¦¬μ†ŒμŠ€ 생성

🎯 섀계 원칙

  • Request: μ„œλ²„κ°€ μžλ™ μƒμ„±ν•˜λŠ” κ°’(ID, μƒμ„±μΌμ‹œ)을 μ œμ™Έν•œ λͺ¨λ“  ν•„μˆ˜ 정보
  • Response: μ„œλ²„μ—μ„œ μƒμ„±λœ 값을 ν¬ν•¨ν•œ μ™„μ „ν•œ λ¦¬μ†ŒμŠ€ μƒνƒœ

πŸ’» μ˜ˆμ‹œ: μƒν’ˆ 등둝 API

POST /api/products

πŸ“€ Request Body

{
    "productName": "μ‹ μ„ ν•œ λͺ©μž₯ 우유 1L",
    "productSaleCost": 2500,
    "initialStockCount": 50,
    "expirationDate": "2025-12-31"
}

πŸ“₯ Response Body (201 Created)

{
    "productId": "PROD-001",
    "productName": "μ‹ μ„ ν•œ λͺ©μž₯ 우유 1L",
    "productSaleCost": 2500,
    "totalStockCount": 50,
    "expirationDate": "2025-12-31",
    "createdAt": "2025-08-09T10:30:00",
    "updatedAt": "2025-08-09T10:30:00",
    "status": "ACTIVE"
}

πŸ” READ (GET) - λ¦¬μ†ŒμŠ€ 쑰회

🎯 섀계 원칙

  • Request: Body μ‚¬μš©ν•˜μ§€ μ•ŠμŒ, λͺ¨λ“  쑰건은 URL νŒŒλΌλ―Έν„°λ‘œ
  • Response: 단일 객체 {} λ˜λŠ” λ°°μ—΄ [] ν˜•νƒœλ‘œ λ°˜ν™˜

πŸ’» μ˜ˆμ‹œ: μƒν’ˆ 쑰회 API

단일 μƒν’ˆ 쑰회

GET /api/products/PROD-001

πŸ“₯ Response Body (200 OK)

{
    "productId": "PROD-001",
    "productName": "μ‹ μ„ ν•œ λͺ©μž₯ 우유 1L",
    "productSaleCost": 2500,
    "totalStockCount": 50,
    "status": "ACTIVE"
}

μƒν’ˆ λͺ©λ‘ 쑰회

GET /api/products?name=우유&status=ACTIVE&page=0&size=10

πŸ“₯ Response Body (200 OK)

{
    "content": [
        {
            "productId": "PROD-001",
            "productName": "μ‹ μ„ ν•œ λͺ©μž₯ 우유 1L",
            "productSaleCost": 2500,
            "status": "ACTIVE"
        },
        {
            "productId": "PROD-002", 
            "productName": "μ €μ§€ 우유 500ml",
            "productSaleCost": 1800,
            "status": "ACTIVE"
        }
    ],
    "totalElements": 2,
    "totalPages": 1,
    "currentPage": 0
}

✏️ UPDATE (PATCH/PUT) - λ¦¬μ†ŒμŠ€ μˆ˜μ •

🎯 섀계 원칙

  • PATCH: λ³€κ²½ν•  ν•„λ“œλ§Œ 전솑 (λΆ€λΆ„ μ—…λ°μ΄νŠΈ)
  • PUT: λ¦¬μ†ŒμŠ€ 전체 ꡐ체
  • Response: μˆ˜μ • μ™„λ£Œλœ 전체 λ¦¬μ†ŒμŠ€ μƒνƒœ

πŸ’» μ˜ˆμ‹œ: μƒν’ˆ μˆ˜μ • API

PATCH /api/products/PROD-001

πŸ“€ Request Body

{
    "productSaleCost": 2800,
    "totalStockCount": 30
}

πŸ“₯ Response Body (200 OK)

{
    "productId": "PROD-001",
    "productName": "μ‹ μ„ ν•œ λͺ©μž₯ 우유 1L",
    "productSaleCost": 2800,
    "totalStockCount": 30,
    "expirationDate": "2025-12-31",
    "updatedAt": "2025-08-09T15:45:00",
    "status": "ACTIVE"
}

πŸ—‘οΈ DELETE - λ¦¬μ†ŒμŠ€ μ‚­μ œ

🎯 섀계 원칙

  • Request: Body μ‚¬μš©ν•˜μ§€ μ•ŠμŒ
  • Response: 빈 Body + 204 No Content μƒνƒœ μ½”λ“œ

πŸ’» μ˜ˆμ‹œ: μƒν’ˆ μ‚­μ œ API

DELETE /api/products/PROD-001

πŸ“₯ Response (204 No Content)

(λΉ„μ–΄μžˆμŒ)

πŸš€ Spring Boot μ‹€μ „ 적용 μ˜ˆμ‹œ

πŸ“ ν”„λ‘œμ νŠΈ κ΅¬μ‘°μ—μ„œ DTO ν™œμš©

// Request DTO
@Data
@NoArgsConstructor
public class ProductCreateRequest {
    @NotBlank(message = "μƒν’ˆλͺ…은 ν•„μˆ˜μž…λ‹ˆλ‹€")
    private String productName;
    
    @Min(value = 0, message = "가격은 0 이상이어야 ν•©λ‹ˆλ‹€")
    private Integer productSaleCost;
    
    @Min(value = 0, message = "μž¬κ³ λŠ” 0 이상이어야 ν•©λ‹ˆλ‹€") 
    private Integer initialStockCount;
    
    @Future(message = "μœ ν†΅κΈ°ν•œμ€ 미래 λ‚ μ§œμ—¬μ•Ό ν•©λ‹ˆλ‹€")
    private LocalDate expirationDate;
}

// Response DTO
@Data
@Builder
public class ProductResponse {
    private String productId;
    private String productName;
    private Integer productSaleCost;
    private Integer totalStockCount;
    private LocalDate expirationDate;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    private ProductStatus status;
}

🎯 Controllerμ—μ„œμ˜ ν™œμš©

@RestController
@RequestMapping("/api/products")
public class ProductController {
    
    @PostMapping
    public ResponseEntity<ProductResponse> createProduct(
            @Valid @RequestBody ProductCreateRequest request) {
        
        ProductResponse response = productService.createProduct(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(response);
    }
    
    @GetMapping("/{productId}")
    public ResponseEntity<ProductResponse> getProduct(
            @PathVariable String productId) {
        
        ProductResponse response = productService.getProduct(productId);
        return ResponseEntity.ok(response);
    }
    
    @PatchMapping("/{productId}")
    public ResponseEntity<ProductResponse> updateProduct(
            @PathVariable String productId,
            @RequestBody ProductUpdateRequest request) {
        
        ProductResponse response = productService.updateProduct(productId, request);
        return ResponseEntity.ok(response);
    }
    
    @DeleteMapping("/{productId}")
    public ResponseEntity<Void> deleteProduct(@PathVariable String productId) {
        productService.deleteProduct(productId);
        return ResponseEntity.noContent().build();
    }
}

βœ… API λͺ…μ„Έμ„œ μž‘μ„± 체크리슀트

πŸ“ Request Body 체크포인트

  • POST: μžλ™ 생성 κ°’(ID, μƒμ„±μΌμ‹œ) μ œμ™Έν•œ λͺ¨λ“  ν•„μˆ˜ 정보 포함
  • GET/DELETE: Body μ‚¬μš©ν•˜μ§€ μ•ŠμŒ
  • PATCH: λ³€κ²½ν•  ν•„λ“œλ§Œ 포함
  • PUT: 전체 λ¦¬μ†ŒμŠ€ 정보 포함
  • Validation: @Valid, @NotNull λ“± 검증 μ–΄λ…Έν…Œμ΄μ…˜ 적용

πŸ“€ Response Body 체크포인트

  • POST/PATCH/PUT: μ™„μ „ν•œ λ¦¬μ†ŒμŠ€ μƒνƒœ λ°˜ν™˜
  • GET: 단일 객체 {} λ˜λŠ” λͺ©λ‘ λ°°μ—΄ [] λ°˜ν™˜
  • DELETE: 빈 Body + 204 μƒνƒœ μ½”λ“œ
  • νŽ˜μ΄μ§•: content, totalElements, totalPages λ“± 메타데이터 포함
  • μ—λŸ¬ 응닡: μΌκ΄€λœ μ—λŸ¬ 응닡 ν˜•μ‹ 적용

🎯 곡톡 섀계 원칙

  • 일관성: ν”„λ‘œμ νŠΈ μ „μ²΄μ—μ„œ λ™μΌν•œ 넀이밍 μ»¨λ²€μ…˜ μ‚¬μš©
  • λ³΄μ•ˆ: λ―Όκ°ν•œ 정보(νŒ¨μŠ€μ›Œλ“œ, 토큰 λ“±) μ‘λ‹΅μ—μ„œ μ œμ™Έ
  • μ„±λŠ₯: λΆˆν•„μš”ν•œ ν•„λ“œ μ œμ™Έ, ν•„μš”μ‹œ 별도 API 제곡
  • λ¬Έμ„œν™”: Swagger/OpenAPI 등을 ν™œμš©ν•œ λͺ…μ„Έμ„œ μžλ™ν™”

πŸ’‘ 핡심 정리

🎯 RESTful API Body μ„€κ³„μ˜ 핡심

  1. 각 HTTP Method의 λͺ©μ μ— λ§žλŠ” Body 섀계
  2. RequestλŠ” μ΅œμ†Œν•œ, ResponseλŠ” μ™„μ „ν•˜κ²Œ
  3. μΌκ΄€λœ νŒ¨ν„΄μœΌλ‘œ 예츑 κ°€λŠ₯ν•œ API
  4. Spring Boot의 DTO/Entity 뢄리 원칙 μ€€μˆ˜

이제 Java Spring λ°±μ—”λ“œ ν”„λ‘œμ νŠΈμ—μ„œ μžμ‹  있게 API λͺ…μ„Έμ„œλ₯Ό μž‘μ„±ν•  수 μžˆμ„ κ±°μ˜ˆμš”! πŸš€


이 κ°€μ΄λ“œκ°€ 도움이 λ˜μ—ˆλ‹€λ©΄, λ‹€μŒμ—λŠ” μ—λŸ¬ μ²˜λ¦¬μ™€ μƒνƒœ μ½”λ“œ ν™œμš©λ²•λ„ μ •λ¦¬ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€! πŸ‘‹