Back to catalog

rest-api-expert

Spring REST API Layer 전문가. Controller, DTO, Exception 처리, Mapper 패턴을 준수하는 REST API 코드를 생성하고 검증합니다. OpenAPI/Swagger 문서화, 일관된 Error Response, HTTP 상태 코드 정확한 사용을 보장합니다.

  • Category: discovered
  • Tags: github, community
  • Version: 1.0.0
  • Author: walid
  • Downloads: 0
  • Updated: 2025-11-22

Download the latest version (v1.0.0) from this page.

SKILL.md

---
name: "rest-api-expert"
description: "Spring REST API Layer 전문가. Controller, DTO, Exception 처리, Mapper 패턴을 준수하는 REST API 코드를 생성하고 검증합니다. OpenAPI/Swagger 문서화, 일관된 Error Response, HTTP 상태 코드 정확한 사용을 보장합니다."
---

# Spring REST API Layer Expert

Spring Boot REST API Layer (Adapter-In) 전문가 Skill입니다. HTTP 인터페이스 설계, 요청/응답 처리, 예외 핸들링, API 문서화를 담당합니다.

## 전문 분야

1. **Controller 설계**: REST 표준, HTTP 메서드, 상태 코드
2. **DTO 패턴**: Request/Response DTO, Validation
3. **Exception 처리**: Global Handler, Error Response, Custom Error Code
4. **Mapper 패턴**: DTO ↔ Domain/Command 변환
5. **API 문서화**: OpenAPI/Swagger, Spring REST Docs

## 사용 시점

- REST API Controller 생성 또는 수정
- API 요청/응답 DTO 설계
- Exception 처리 구현
- API 문서화 필요 시

## 핵심 규칙

### 1. Controller 설계 원칙

**위치**: `docs/coding_convention/01-adapter-rest-api-layer/controller-design/`

```java
@RestController
@RequestMapping("/api/v1/orders")
@RequiredArgsConstructor
@Validated
public class OrderController {
    private final PlaceOrderUseCase placeOrderUseCase;
    private final OrderMapper orderMapper;

    @PostMapping
    public ResponseEntity<OrderResponse> placeOrder(
        @Valid @RequestBody OrderRequest request
    ) {
        PlaceOrderCommand command = orderMapper.toCommand(request);
        OrderResult result = placeOrderUseCase.execute(command);
        return ResponseEntity
            .status(HttpStatus.CREATED)
            .body(orderMapper.toResponse(result));
    }
}
```

**규칙**:
- ✅ `@RestController` + `@RequestMapping` 조합
- ✅ UseCase 의존성 주입 (Constructor Injection)
- ✅ `@Valid` + `@RequestBody` 검증
- ✅ HTTP 상태 코드 정확히 사용 (POST → 201 CREATED)
- ✅ Mapper를 통한 DTO ↔ Command 변환

### 2. DTO 패턴

**Request DTO** (`dto/request/`):
```java
public record OrderRequest(
    @NotBlank(message = "주문 번호는 필수입니다")
    String orderNumber,

    @NotNull(message = "고객 ID는 필수입니다")
    @Positive(message = "고객 ID는 양수여야 합니다")
    Long customerId,

    @NotEmpty(message = "주문 항목은 1개 이상이어야 합니다")
    List<OrderItemRequest> items
) {
    // Compact Constructor (Validation)
    public OrderRequest {
        Objects.requireNonNull(orderNumber);
        Objects.requireNonNull(customerId);
        items = List.copyOf(items);  // Immutable
    }
}
```

**Response DTO** (`dto/response/`):
```java
public record OrderResponse(
    Long orderId,
    String orderNumber,
    String status,
    LocalDateTime createdAt
) {
    // Response는 Validation 불필요
}
```

**규칙**:
- ✅ Record 패턴 사용 (Java 21)
- ✅ Jakarta Validation (`@NotBlank`, `@NotNull`, `@Positive` 등)
- ✅ Compact Constructor로 추가 검증
- ✅ Immutable (final fields, List.copyOf)

### 3. Exception 처리

**Global Exception Handler** (`exception/GlobalExceptionHandler.java`):
```java
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(DomainException.class)
    public ResponseEntity<ErrorResponse> handleDomainException(
        DomainException ex
    ) {
        log.warn("Domain exception: {}", ex.getMessage());
        return ResponseEntity
            .status(ex.getHttpStatus())
            .body(ErrorResponse.of(ex));
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidationException(
        MethodArgumentNotValidException ex
    ) {
        log.warn("Validation failed: {}", ex.getMessage());
        return ResponseEntity
            .status(HttpStatus.BAD_REQUEST)
            .body(ErrorResponse.ofValidation(ex));
    }
}
```

**Error Response** (`dto/response/ErrorResponse.java`):
```java
public record ErrorResponse(
    String errorCode,
    String message,
    LocalDateTime timestamp,
    List<FieldError> fieldErrors
) {
    public static ErrorResponse of(DomainException ex) {
        return new ErrorResponse(
            ex.getErrorCode(),
            ex.getMessage(),
            LocalDateTime.now(),
            Collections.emptyList()
        );
    }

    public record FieldError(String field, String message) {}
}
```

**규칙**:
- ✅ `@RestControllerAdvice` 사용
- ✅ Domain Exception → HTTP 상태 코드 매핑
- ✅ Validation Exception → 400 Bad Request
- ✅ 일관된 Error Response 형식

### 4. Mapper 패턴

**Request Mapper** (`mapper/OrderRequestMapper.java`):
```java
@Component
public class OrderRequestMapper {

    public PlaceOrderCommand toCommand(OrderRequest request) {
        return new PlaceOrderCommand(
            request.orderNumber(),
            request.customerId(),
            request.items().stream()
                .map(this::toItemCommand)
                .toList()
        );
    }

    private OrderItemCommand toItemCommand(OrderItemRequest item) {
        return new OrderItemCommand(
            item.productId(),
            item.quantity(),
            item.price()
        );
    }
}
```

**Response Mapper** (`mapper/OrderResponseMapper.java`):
```java
@Component
public class OrderResponseMapper {

    public OrderResponse toResponse(OrderResult result) {
        return new OrderResponse(
            result.orderId(),
            result.orderNumber(),
            result.status().name(),
            result.createdAt()
        );
    }
}
```

**규칙**:
- ✅ `@Component` 등록
- ✅ Request → Command 변환 (UseCase 입력)
- ✅ Result → Response 변환 (클라이언트 출력)
- ✅ 복잡한 변환은 private 메서드로 분리

### 5. API 문서화

**OpenAPI/Swagger** (`config/OpenApiConfig.java`):
```java
@Configuration
public class OpenApiConfig {

    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
            .info(new Info()
                .title("Order Management API")
                .version("v1.0.0")
                .description("주문 관리 REST API"));
    }
}
```

**Controller 문서화**:
```java
@Operation(summary = "주문 생성", description = "새로운 주문을 생성합니다")
@ApiResponses({
    @ApiResponse(
        responseCode = "201",
        description = "주문 생성 성공",
        content = @Content(schema = @Schema(implementation = OrderResponse.class))
    ),
    @ApiResponse(
        responseCode = "400",
        description = "잘못된 요청",
        content = @Content(schema = @Schema(implementation = ErrorResponse.class))
    )
})
@PostMapping
public ResponseEntity<OrderResponse> placeOrder(
    @Valid @RequestBody OrderRequest request
) {
    // ...
}
```

## 패키지 구조

```
adapter-in/rest-api/
├── controller/
│   ├── OrderController.java
│   └── CustomerController.java
├── dto/
│   ├── request/
│   │   ├── OrderRequest.java
│   │   └── OrderItemRequest.java
│   └── response/
│       ├── OrderResponse.java
│       └── ErrorResponse.java
├── exception/
│   ├── GlobalExceptionHandler.java
│   └── ErrorCode.java
├── mapper/
│   ├── OrderRequestMapper.java
│   └── OrderResponseMapper.java
└── config/
    ├── OpenApiConfig.java
    └── WebMvcConfig.java
```

## HTTP 상태 코드 가이드

| 작업 | 성공 상태 코드 | 실패 상태 코드 |
|------|---------------|----------------|
| POST (생성) | 201 CREATED | 400 Bad Request, 409 Conflict |
| GET (조회) | 200 OK | 404 Not Found |
| PUT (전체 수정) | 200 OK | 400 Bad Request, 404 Not Found |
| PATCH (부분 수정) | 200 OK | 400 Bad Request, 404 Not Found |
| DELETE (삭제) | 204 No Content | 404 Not Found, 409 Conflict |

## 검증 체크리스트

REST API 코드 작성 후 다음을 확인하세요:

- [ ] `@RestController` + `@RequestMapping` 사용
- [ ] UseCase 의존성 Constructor Injection
- [ ] Request DTO는 Record + Validation
- [ ] Response DTO는 Record (Immutable)
- [ ] HTTP 상태 코드 정확히 사용
- [ ] Exception은 GlobalExceptionHandler에서 처리
- [ ] ErrorResponse 형식 일관성 유지
- [ ] Mapper로 DTO ↔ Command 변환
- [ ] OpenAPI/Swagger 문서화
- [ ] 통합 테스트 작성 (MockMvc)

## 추가 리소스

상세 규칙은 다음 파일을 참조하세요:

```bash
cat .claude/skills/rest-api-expert/REFERENCE.md
```

검증 스크립트:

```bash
bash .claude/skills/rest-api-expert/scripts/validate-rest-api.sh [file_path]
```

## 참고 문서

- `docs/coding_convention/01-adapter-rest-api-layer/`
- `docs/coding_convention/01-adapter-rest-api-layer/package-guide/01_rest_api_package_guide.md`
- `docs/coding_convention/01-adapter-rest-api-layer/controller-design/01_rest-api-conventions.md`
- `docs/coding_convention/01-adapter-rest-api-layer/dto-patterns/`
- `docs/coding_convention/01-adapter-rest-api-layer/exception-handling/`