Back to Skills

controller-expert

majiayu000
Updated Today
1 views
58
9
58
View on GitHub
Metaprojectrest-apicontrollerdtovalidationrestfulcqrs

About

This skill generates REST API controllers following Thin Controller and CQRS patterns, separating command and query operations. It enforces specific conventions including ResponseEntity<ApiResponse<T>> wrapping, @Valid validation, and PATCH over DELETE operations. Use it when building REST API layers to ensure standardized controller structure with UseCase dependencies and proper testing via TestRestTemplate.

Quick Install

Claude Code

Recommended
Plugin CommandRecommended
/plugin add https://github.com/majiayu000/claude-skill-registry
Git CloneAlternative
git clone https://github.com/majiayu000/claude-skill-registry.git ~/.claude/skills/controller-expert

Copy and paste this command in Claude Code to install this skill

Documentation

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.javaadapter-in/rest-api/{bc}/controller/
QueryController{Bc}QueryController.javaadapter-in/rest-api/{bc}/controller/
Command DTO{Action}{Bc}ApiRequest.javaadapter-in/rest-api/{bc}/dto/command/
Query DTO{Bc}SearchApiRequest.javaadapter-in/rest-api/{bc}/dto/query/
Response DTO{Bc}ApiResponse.javaadapter-in/rest-api/{bc}/dto/response/
ApiMapper{Bc}ApiMapper.javaadapter-in/rest-api/{bc}/mapper/
ErrorMapper{Bc}ApiErrorMapper.javaadapter-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 메서드접두사예시
POSTCreate, Register, PlaceCreateOrderApiRequest
PATCHUpdate, Modify, CancelCancelOrderApiRequest

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 상태용도
POST201 Created리소스 생성 성공
GET200 OK조회 성공
PATCH200 OK부분 수정 성공
PUT200 OK전체 수정 성공
DELETE❌ 지원 안 함소프트 삭제는 PATCH로

Zero-Tolerance 규칙

✅ MANDATORY (필수)

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

❌ PROHIBITED (금지)

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

페이징 패턴

Slice vs Page 선택

구분SliceApiResponsePageApiResponse
사용 사례무한 스크롤 (일반 사용자)페이지 번호 (관리자)
성능빠름 (COUNT 불필요)느림 (COUNT 필수)
제공 정보hasNext, nextCursortotalElements, 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

GitHub Repository

majiayu000/claude-skill-registry
Path: skills/controller-expert

Related Skills