Claude Code Plugins

Community-maintained marketplace

Feedback

controller-expert

@ryu-qqq/AuthHub
0
0

|

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name controller-expert
version 3.0.0
description REST Controller 전문가. Thin Controller 패턴, CQRS Command/Query Controller 분리, ResponseEntity<ApiResponse<T>> 래핑, @Valid 검증 필수. DELETE 금지(PATCH 사용). TestRestTemplate 필수 (MockMvc 금지). UseCase 의존. Mapper DI.
author claude-spring-standards
created Fri Nov 01 2024 00:00:00 GMT+0000 (Coordinated Universal Time)
updated Fri Dec 05 2025 00:00:00 GMT+0000 (Coordinated Universal Time)
tags project, rest-api, controller, dto, validation, restful, cqrs

Controller Expert (REST API 전문가)

목적 (Purpose)

REST API Layer에서 HTTP 요청/응답을 처리하는 컴포넌트를 규칙에 맞게 생성합니다. Thin Controller 패턴과 CQRS 분리 원칙을 준수합니다.

활성화 조건

  • /impl rest-api {feature} 명령 실행 시
  • /plan 실행 후 REST API Layer 작업 시
  • controller, rest, dto, request, response, api 키워드 언급 시

산출물 (Output)

컴포넌트 파일명 패턴 위치
CommandController {Bc}CommandController.java adapter-in/rest-api/{bc}/controller/
QueryController {Bc}QueryController.java adapter-in/rest-api/{bc}/controller/
Command DTO {Action}{Bc}ApiRequest.java adapter-in/rest-api/{bc}/dto/command/
Query DTO {Bc}SearchApiRequest.java adapter-in/rest-api/{bc}/dto/query/
Response DTO {Bc}ApiResponse.java adapter-in/rest-api/{bc}/dto/response/
ApiMapper {Bc}ApiMapper.java adapter-in/rest-api/{bc}/mapper/
ErrorMapper {Bc}ApiErrorMapper.java adapter-in/rest-api/{bc}/error/

완료 기준 (Acceptance Criteria)

  • CQRS 분리: CommandController (POST, PATCH) / QueryController (GET)
  • ResponseEntity<ApiResponse<T>> 래핑 (두 가지 모두 필수)
  • @Valid 검증: 모든 Request DTO에 필수
  • UseCase 직접 의존 (Service 금지)
  • Mapper DI: @Component Bean (Static 금지)
  • DELETE 금지: 소프트 삭제는 PATCH /{id}/delete
  • Lombok 금지
  • DTO는 Java 21 Record
  • ArchUnit 테스트 통과

Thin Controller 패턴

HTTP Request
    ↓
@Valid 검증 (Request DTO)
    ↓
Mapper 변환 (API DTO → UseCase DTO)
    ↓
UseCase 실행
    ↓
Mapper 변환 (UseCase DTO → API DTO)
    ↓
ResponseEntity<ApiResponse<T>> 래핑
    ↓
HTTP Response

핵심: Controller는 HTTP 요청/응답 처리만. 비즈니스 로직 절대 금지.


코드 템플릿

1. CommandController (POST, PATCH)

package com.ryuqq.adapter.in.rest.order.controller;

import com.ryuqq.adapter.in.rest.common.dto.ApiResponse;
import com.ryuqq.adapter.in.rest.order.dto.command.CreateOrderApiRequest;
import com.ryuqq.adapter.in.rest.order.dto.command.CancelOrderApiRequest;
import com.ryuqq.adapter.in.rest.order.dto.response.OrderApiResponse;
import com.ryuqq.adapter.in.rest.order.mapper.OrderApiMapper;
import com.ryuqq.application.order.port.in.CreateOrderUseCase;
import com.ryuqq.application.order.port.in.CancelOrderUseCase;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * Order Command Controller
 *
 * <p>Order 도메인의 상태 변경 API를 제공합니다.</p>
 *
 * <p>제공하는 API:</p>
 * <ul>
 *   <li>POST /api/v1/orders - 주문 생성</li>
 *   <li>PATCH /api/v1/orders/{id}/cancel - 주문 취소</li>
 * </ul>
 *
 * @author development-team
 * @since 1.0.0
 */
@RestController
@RequestMapping("${api.endpoints.base-v1}/orders")
@Validated
public class OrderCommandController {

    private final CreateOrderUseCase createOrderUseCase;
    private final CancelOrderUseCase cancelOrderUseCase;
    private final OrderApiMapper orderApiMapper;

    public OrderCommandController(
            CreateOrderUseCase createOrderUseCase,
            CancelOrderUseCase cancelOrderUseCase,
            OrderApiMapper orderApiMapper) {
        this.createOrderUseCase = createOrderUseCase;
        this.cancelOrderUseCase = cancelOrderUseCase;
        this.orderApiMapper = orderApiMapper;
    }

    /**
     * 주문 생성
     *
     * @param request 주문 생성 요청 DTO
     * @return 주문 생성 결과 (201 Created)
     */
    @PostMapping
    public ResponseEntity<ApiResponse<OrderApiResponse>> createOrder(
            @RequestBody @Valid CreateOrderApiRequest request) {

        // 1. API Request → UseCase Command 변환 (Mapper)
        var command = orderApiMapper.toCommand(request);

        // 2. UseCase 실행 (비즈니스 로직)
        var useCaseResponse = createOrderUseCase.execute(command);

        // 3. UseCase Response → API Response 변환 (Mapper)
        var apiResponse = orderApiMapper.toApiResponse(useCaseResponse);

        // 4. ResponseEntity<ApiResponse<T>> 래핑
        return ResponseEntity
            .status(HttpStatus.CREATED)
            .body(ApiResponse.ofSuccess(apiResponse));
    }

    /**
     * 주문 취소 (소프트 삭제 패턴)
     *
     * @param id 주문 ID
     * @param request 취소 요청 DTO
     * @return 취소 결과 (200 OK)
     */
    @PatchMapping("/{id}/cancel")
    public ResponseEntity<ApiResponse<Void>> cancelOrder(
            @PathVariable Long id,
            @RequestBody @Valid CancelOrderApiRequest request) {

        var command = orderApiMapper.toCancelCommand(id, request);
        cancelOrderUseCase.execute(command);

        return ResponseEntity.ok(ApiResponse.ofSuccess());
    }
}

핵심 규칙:

  • POST: 201 Created 반환
  • PATCH: 200 OK 반환
  • DELETE 금지 → PATCH /{id}/delete 또는 PATCH /{id}/cancel
  • @Validated 클래스 레벨 (PathVariable/RequestParam 검증용)
  • @Valid 파라미터 레벨 (RequestBody 검증용)

2. QueryController (GET)

package com.ryuqq.adapter.in.rest.order.controller;

import com.ryuqq.adapter.in.rest.common.dto.ApiResponse;
import com.ryuqq.adapter.in.rest.common.dto.SliceApiResponse;
import com.ryuqq.adapter.in.rest.order.dto.query.OrderSearchApiRequest;
import com.ryuqq.adapter.in.rest.order.dto.response.OrderApiResponse;
import com.ryuqq.adapter.in.rest.order.dto.response.OrderDetailApiResponse;
import com.ryuqq.adapter.in.rest.order.mapper.OrderApiMapper;
import com.ryuqq.application.order.port.in.GetOrderQueryService;
import com.ryuqq.application.order.port.in.SearchOrderQueryService;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Positive;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * Order Query Controller
 *
 * <p>Order 도메인의 조회 API를 제공합니다.</p>
 *
 * <p>제공하는 API:</p>
 * <ul>
 *   <li>GET /api/v1/orders/{id} - 주문 단건 조회</li>
 *   <li>GET /api/v1/orders - 주문 검색</li>
 * </ul>
 *
 * @author development-team
 * @since 1.0.0
 */
@RestController
@RequestMapping("${api.endpoints.base-v1}/orders")
@Validated
public class OrderQueryController {

    private final GetOrderQueryService getOrderQueryService;
    private final SearchOrderQueryService searchOrderQueryService;
    private final OrderApiMapper orderApiMapper;

    public OrderQueryController(
            GetOrderQueryService getOrderQueryService,
            SearchOrderQueryService searchOrderQueryService,
            OrderApiMapper orderApiMapper) {
        this.getOrderQueryService = getOrderQueryService;
        this.searchOrderQueryService = searchOrderQueryService;
        this.orderApiMapper = orderApiMapper;
    }

    /**
     * 주문 단건 조회
     *
     * @param id 주문 ID (양수)
     * @return 주문 상세 정보 (200 OK)
     */
    @GetMapping("/{id}")
    public ResponseEntity<ApiResponse<OrderDetailApiResponse>> getOrder(
            @PathVariable @Positive Long id) {

        // 1. ID → UseCase Query 변환 (Mapper)
        var query = orderApiMapper.toGetQuery(id);

        // 2. UseCase 실행 (조회 로직)
        var useCaseResponse = getOrderQueryService.getById(query);

        // 3. UseCase Response → API Response 변환 (Mapper)
        var apiResponse = orderApiMapper.toDetailApiResponse(useCaseResponse);

        // 4. ResponseEntity<ApiResponse<T>> 래핑
        return ResponseEntity.ok(ApiResponse.ofSuccess(apiResponse));
    }

    /**
     * 주문 검색 (Cursor 기반)
     *
     * @param request 검색 조건
     * @return 주문 검색 결과 (200 OK)
     */
    @GetMapping
    public ResponseEntity<ApiResponse<SliceApiResponse<OrderApiResponse>>> searchOrders(
            @Valid @ModelAttribute OrderSearchApiRequest request) {

        var query = orderApiMapper.toSearchQuery(request);
        var useCaseResponse = searchOrderQueryService.search(query);
        var apiResponse = SliceApiResponse.from(
            useCaseResponse,
            orderApiMapper::toApiResponse
        );

        return ResponseEntity.ok(ApiResponse.ofSuccess(apiResponse));
    }
}

핵심 규칙:

  • GET: 200 OK 반환
  • @ModelAttribute로 Query Parameter 바인딩
  • @Positive로 PathVariable 검증
  • SliceApiResponse (Cursor) vs PageApiResponse (Offset) 선택

3. Command DTO (Request)

package com.ryuqq.adapter.in.rest.order.dto.command;

import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import java.util.List;

/**
 * 주문 생성 요청
 *
 * @param customerId 고객 ID
 * @param items 주문 항목 목록
 * @param shippingAddress 배송 주소
 * @author development-team
 * @since 1.0.0
 */
public record CreateOrderApiRequest(
    @NotNull(message = "고객 ID는 필수입니다")
    Long customerId,

    @NotEmpty(message = "주문 항목은 필수입니다")
    @Valid
    List<OrderItemRequest> items,

    @NotNull(message = "배송 주소는 필수입니다")
    @Valid
    AddressRequest shippingAddress
) {
    // Compact Constructor (불변 리스트, 추가 검증)
    public CreateOrderApiRequest {
        if (customerId != null && customerId <= 0) {
            throw new IllegalArgumentException("유효하지 않은 고객 ID: " + customerId);
        }
        items = items == null ? List.of() : List.copyOf(items);
    }

    /**
     * 주문 항목 요청
     */
    public record OrderItemRequest(
        @NotNull(message = "상품 ID는 필수입니다")
        Long productId,

        @NotNull(message = "수량은 필수입니다")
        @Min(value = 1, message = "수량은 1 이상이어야 합니다")
        Integer quantity,

        @NotNull(message = "가격은 필수입니다")
        @Min(value = 0, message = "가격은 0 이상이어야 합니다")
        Long price
    ) {}

    /**
     * 배송 주소 요청
     */
    public record AddressRequest(
        @NotBlank(message = "우편번호는 필수입니다")
        String zipCode,

        @NotBlank(message = "주소는 필수입니다")
        String address,

        String addressDetail
    ) {}
}

Command DTO 네이밍 규칙:

HTTP 메서드 접두사 예시
POST Create, Register, Place CreateOrderApiRequest
PATCH Update, Modify, Cancel CancelOrderApiRequest

4. Query DTO (Request)

package com.ryuqq.adapter.in.rest.order.dto.query;

import jakarta.validation.constraints.*;
import java.time.LocalDate;

/**
 * 주문 검색 요청
 *
 * @param customerId 고객 ID (Optional)
 * @param status 주문 상태 (Optional)
 * @param startDate 시작 날짜 (Optional)
 * @param endDate 종료 날짜 (Optional)
 * @param cursor 다음 페이지 커서 (Optional)
 * @param size 페이지 크기
 * @author development-team
 * @since 1.0.0
 */
public record OrderSearchApiRequest(
    // Optional 필드 (null 허용)
    Long customerId,

    @Pattern(regexp = "PLACED|CONFIRMED|SHIPPED|DELIVERED|CANCELLED",
             message = "유효하지 않은 상태입니다")
    String status,

    LocalDate startDate,
    LocalDate endDate,

    // Cursor 기반 페이징
    String cursor,

    // 페이징 필수
    @Min(value = 1, message = "페이지 크기는 1 이상")
    @Max(value = 100, message = "페이지 크기는 100 이하")
    Integer size
) {
    // Compact Constructor (기본값 설정)
    public OrderSearchApiRequest {
        size = size == null ? 20 : size;

        // 날짜 범위 검증
        if (startDate != null && endDate != null && startDate.isAfter(endDate)) {
            throw new IllegalArgumentException("시작 날짜는 종료 날짜보다 이전이어야 합니다");
        }
    }
}

Query DTO 특징:

  • 대부분 필드 Optional (null 허용)
  • 페이징 조건 필수 (무한 조회 방지)
  • Compact Constructor로 기본값 설정

5. Response DTO

package com.ryuqq.adapter.in.rest.order.dto.response;

import java.time.LocalDateTime;
import java.util.List;

/**
 * 주문 응답
 *
 * @param orderId 주문 ID
 * @param customerId 고객 ID
 * @param status 주문 상태
 * @param totalAmount 총 금액
 * @param items 주문 항목 목록
 * @param createdAt 생성 일시
 * @author development-team
 * @since 1.0.0
 */
public record OrderApiResponse(
    Long orderId,
    Long customerId,
    String status,
    Long totalAmount,
    List<OrderItemResponse> items,
    LocalDateTime createdAt
) {
    // Compact Constructor (불변 컬렉션)
    public OrderApiResponse {
        items = items == null ? List.of() : List.copyOf(items);
    }

    /**
     * Application Layer Response → REST API Response
     */
    public static OrderApiResponse from(OrderResponse appResponse) {
        return new OrderApiResponse(
            appResponse.orderId(),
            appResponse.customerId(),
            appResponse.status(),
            appResponse.totalAmount(),
            appResponse.items().stream()
                .map(OrderItemResponse::from)
                .toList(),
            appResponse.createdAt()
        );
    }

    /**
     * 주문 항목 응답
     */
    public record OrderItemResponse(
        Long productId,
        String productName,
        Integer quantity,
        Long price
    ) {
        public static OrderItemResponse from(OrderItemDto dto) {
            return new OrderItemResponse(
                dto.productId(),
                dto.productName(),
                dto.quantity(),
                dto.price()
            );
        }
    }
}

Response DTO 특징:

  • from() 정적 팩토리 메서드로 변환
  • 불변 컬렉션 (List.copyOf)
  • Nested Record로 복잡한 구조 표현

6. ApiMapper (@Component)

package com.ryuqq.adapter.in.rest.order.mapper;

import com.ryuqq.adapter.in.rest.order.dto.command.CreateOrderApiRequest;
import com.ryuqq.adapter.in.rest.order.dto.command.CancelOrderApiRequest;
import com.ryuqq.adapter.in.rest.order.dto.query.OrderSearchApiRequest;
import com.ryuqq.adapter.in.rest.order.dto.response.OrderApiResponse;
import com.ryuqq.adapter.in.rest.order.dto.response.OrderDetailApiResponse;
import com.ryuqq.application.order.dto.command.CreateOrderCommand;
import com.ryuqq.application.order.dto.command.CancelOrderCommand;
import com.ryuqq.application.order.dto.query.GetOrderQuery;
import com.ryuqq.application.order.dto.query.SearchOrdersQuery;
import com.ryuqq.application.order.dto.response.OrderResponse;
import com.ryuqq.application.order.dto.response.OrderDetailResponse;
import org.springframework.stereotype.Component;
import java.util.List;

/**
 * OrderApiMapper - Order REST API ↔ Application Layer 변환
 *
 * <p>REST API Layer와 Application Layer 간의 DTO 변환을 담당합니다.</p>
 *
 * @author development-team
 * @since 1.0.0
 */
@Component
public class OrderApiMapper {

    /**
     * CreateOrderApiRequest → CreateOrderCommand 변환
     */
    public CreateOrderCommand toCommand(CreateOrderApiRequest request) {
        List<CreateOrderCommand.OrderItem> items = request.items().stream()
            .map(item -> CreateOrderCommand.OrderItem.of(
                item.productId(),
                item.quantity(),
                item.price()
            ))
            .toList();

        return CreateOrderCommand.of(
            request.customerId(),
            items,
            toAddressCommand(request.shippingAddress())
        );
    }

    /**
     * CancelOrderApiRequest → CancelOrderCommand 변환
     */
    public CancelOrderCommand toCancelCommand(Long id, CancelOrderApiRequest request) {
        return CancelOrderCommand.of(id, request.reason());
    }

    /**
     * ID → GetOrderQuery 변환
     */
    public GetOrderQuery toGetQuery(Long id) {
        return GetOrderQuery.of(id);
    }

    /**
     * OrderSearchApiRequest → SearchOrdersQuery 변환
     */
    public SearchOrdersQuery toSearchQuery(OrderSearchApiRequest request) {
        boolean isCursor = request.cursor() != null && !request.cursor().isBlank();

        if (isCursor) {
            return SearchOrdersQuery.ofCursor(
                request.customerId(),
                request.status(),
                request.startDate(),
                request.endDate(),
                request.cursor(),
                request.size()
            );
        } else {
            return SearchOrdersQuery.ofOffset(
                request.customerId(),
                request.status(),
                request.startDate(),
                request.endDate(),
                0,  // page default
                request.size()
            );
        }
    }

    /**
     * OrderResponse → OrderApiResponse 변환
     */
    public OrderApiResponse toApiResponse(OrderResponse appResponse) {
        return OrderApiResponse.from(appResponse);
    }

    /**
     * OrderDetailResponse → OrderDetailApiResponse 변환
     */
    public OrderDetailApiResponse toDetailApiResponse(OrderDetailResponse appResponse) {
        return OrderDetailApiResponse.from(appResponse);
    }

    // ========== Private Helper ==========

    private CreateOrderCommand.Address toAddressCommand(
            CreateOrderApiRequest.AddressRequest address) {
        return CreateOrderCommand.Address.of(
            address.zipCode(),
            address.address(),
            address.addressDetail()
        );
    }
}

ApiMapper 규칙:

  • @Component Bean 등록 (Static 금지)
  • 필드 매핑만 수행 (비즈니스 로직 금지)
  • 기본값 설정/검증 금지 (Controller/Bean Validation 책임)

HTTP 상태 코드 규칙

메서드 HTTP 상태 용도
POST 201 Created 리소스 생성 성공
GET 200 OK 조회 성공
PATCH 200 OK 부분 수정 성공
PUT 200 OK 전체 수정 성공
DELETE ❌ 지원 안 함 소프트 삭제는 PATCH로

Zero-Tolerance 규칙

✅ MANDATORY (필수)

규칙 설명
ResponseEntity<ApiResponse<T>> 두 가지 래핑 모두 필수
@Valid 모든 Request DTO에 필수
@Validated Controller 클래스 레벨
UseCase 의존 Service 직접 의존 금지
Mapper DI @Component Bean (Static 금지)
Record DTO Java 21 Record 사용
RESTful URI 명사 복수형, 동사 금지

❌ PROHIBITED (금지)

항목 이유
DELETE 메서드 소프트 삭제는 PATCH 사용
비즈니스 로직 UseCase/Domain 책임
@Transactional UseCase 책임
try-catch GlobalExceptionHandler 위임
Domain 직접 노출 Response DTO 사용
Lombok Plain Java 사용
Jackson 어노테이션 @JsonFormat, @JsonProperty 금지
MockMvc 테스트 TestRestTemplate 사용

페이징 패턴

Slice vs Page 선택

구분 SliceApiResponse PageApiResponse
사용 사례 무한 스크롤 (일반 사용자) 페이지 번호 (관리자)
성능 빠름 (COUNT 불필요) 느림 (COUNT 필수)
제공 정보 hasNext, nextCursor totalElements, totalPages
적합한 UI 모바일, SNS 피드 관리자 테이블
// Slice (일반 사용자)
@GetMapping
public ResponseEntity<ApiResponse<SliceApiResponse<OrderApiResponse>>> searchOrders(...) {
    // hasNext, nextCursor 반환
}

// Page (관리자)
@GetMapping("/admin")
public ResponseEntity<ApiResponse<PageApiResponse<OrderApiResponse>>> searchOrdersForAdmin(...) {
    // totalElements, totalPages 반환
}

에러 처리 (RFC 7807)

GlobalExceptionHandler

  • @RestControllerAdvice로 전역 예외 처리
  • application/problem+json Content-Type
  • x-error-code 응답 헤더 추가
  • 로깅 레벨: 5xx → ERROR, 404 → DEBUG, 4xx → WARN

ErrorMapper 패턴

@Component
public class OrderApiErrorMapper implements ErrorMapper {

    @Override
    public boolean supports(DomainException ex) {
        return ex instanceof OrderException;
    }

    @Override
    public MappedError map(DomainException ex, Locale locale) {
        return switch (ex.code()) {
            case "ORDER_NOT_FOUND" -> new MappedError(
                HttpStatus.NOT_FOUND,
                "Not Found",
                "주문을 찾을 수 없습니다",
                URI.create("/errors/order/not-found")
            );
            // ...
        };
    }
}

패키지 구조

adapter-in/rest-api/
├── common/
│   ├── controller/
│   │   └── GlobalExceptionHandler.java
│   ├── dto/
│   │   ├── ApiResponse.java
│   │   ├── SliceApiResponse.java
│   │   └── PageApiResponse.java
│   ├── error/
│   │   └── ErrorMapperRegistry.java
│   └── mapper/
│       └── ErrorMapper.java
│
└── {bc}/
    ├── controller/
    │   ├── {Bc}CommandController.java
    │   └── {Bc}QueryController.java
    ├── dto/
    │   ├── command/
    │   │   └── {Action}{Bc}ApiRequest.java
    │   ├── query/
    │   │   └── {Bc}SearchApiRequest.java
    │   └── response/
    │       └── {Bc}ApiResponse.java
    ├── mapper/
    │   └── {Bc}ApiMapper.java
    └── error/
        └── {Bc}ApiErrorMapper.java

체크리스트 (Output Checklist)

Controller

  • @RestController, @RequestMapping 어노테이션
  • @Validated 클래스 레벨
  • UseCase, Mapper 생성자 주입 (Lombok 없음)
  • ResponseEntity<ApiResponse<T>> 반환
  • HTTP 상태 코드 올바르게 설정 (POST → 201)
  • @Valid 모든 Request DTO에 적용
  • DELETE 미사용 (PATCH로 대체)
  • 비즈니스 로직 없음
  • Javadoc 작성

Command DTO

  • public record 키워드
  • {Action}{Bc}ApiRequest 네이밍
  • Bean Validation 어노테이션
  • Compact Constructor (불변 리스트, 추가 검증)
  • Nested Record (복잡한 구조)

Query DTO

  • public record 키워드
  • {Bc}SearchApiRequest 네이밍
  • Optional 필드 (대부분 null 허용)
  • 페이징 조건 필수 (size)
  • 기본값 설정 (Compact Constructor)

Response DTO

  • public record 키워드
  • {Bc}ApiResponse 네이밍
  • from() 정적 팩토리 메서드
  • 불변 컬렉션 (List.copyOf)

ApiMapper

  • @Component 어노테이션
  • 생성자 주입 (필요 시)
  • 필드 매핑만 수행
  • 비즈니스 로직 없음
  • Static 메서드 없음

테스트 체크리스트

Controller 통합 테스트

  • @SpringBootTest(webEnvironment = RANDOM_PORT)
  • TestRestTemplate 사용 (MockMvc 금지)
  • @Tag("integration"), @Tag("adapter-rest")
  • 성공/실패 시나리오 테스트
  • Validation 실패 테스트

REST Docs 테스트

  • RestDocsTestSupport 상속
  • document() 메서드로 Snippet 생성
  • requestFields(), responseFields() 문서화
  • pathParameters(), queryParameters() 문서화

참조 문서

  • Controller Guide: docs/coding_convention/01-adapter-in-layer/rest-api/controller/controller-guide.md
  • Command DTO Guide: docs/coding_convention/01-adapter-in-layer/rest-api/dto/command/command-dto-guide.md
  • Query DTO Guide: docs/coding_convention/01-adapter-in-layer/rest-api/dto/query/query-dto-guide.md
  • Response DTO Guide: docs/coding_convention/01-adapter-in-layer/rest-api/dto/response/response-dto-guide.md
  • Mapper Guide: docs/coding_convention/01-adapter-in-layer/rest-api/mapper/mapper-guide.md
  • Error Guide: docs/coding_convention/01-adapter-in-layer/rest-api/error/error-guide.md
  • REST Docs Guide: docs/coding_convention/01-adapter-in-layer/rest-api/controller/controller-test-restdocs-guide.md