[프롬프트] REST API 설계 가이드 프롬프트 - 최적 프롬프트 레시피

REST API 설계 가이드 및 API 거버넌스

[프롬프트] REST API 설계 가이드 프롬프트 - 최적 프롬프트 레시피

💡 한 줄 요약: "REST API 설계해줘"라는 단순 질의 시 발생하는 URI 내 동사 남발(/getUser, /deleteOrder), HTTP 메서드 오용, 비표준 에러 응답 문제를 원천 차단하고, Principal API Governance Architect 및 Staff Backend Tech Lead 관점에서 명사 복수형 리소스, RFC 7807/9457 에러 스키마, 멱등성(Idempotency-Key), 커서 페이지네이션, 웹훅 서명 검증까지 완벽히 갖춘 프로덕션급 API 명세를 도출하는 최적 프롬프트 레시피입니다.


1. 페인포인트 & 작업 목표

• 실무 상황: 신규 마이크로서비스나 비즈니스 도메인의 API를 킥오프할 때, 명확한 거버넌스 가이드라인 없이 개발팀 각자의 관습대로 엔드포인트를 설계하여 일관성이 깨지고, 프론트엔드 연동 장애 및 분산 트랜잭션 불일치가 발생하는 상황.
• 단순 프롬프트 요청 시 발생하는 4대 전형적 문제:
1. URI 내 동사 남발 및 RPC 스타일 변질: /api/getUsers, /api/updateOrderStatus, /api/deleteItem처럼 리소스 중심이 아닌 동사 기반 RPC 경로를 무분별하게 생성함.
2. HTTP 메서드 및 상태 코드 오용: 수정 요청에 일괄 POST를 쓰거나, 비즈니스 에러가 발생했음에도 HTTP 200 OK에 { "success": false, "code": -1 } 형태로 응답하는 안티패턴 양산.
3. 분산 환경의 결제·주문 멱등성(Idempotency) 결여: 네트워크 재시도나 중복 클릭 시 중복 결제/주문이 발생하는 위험을 방어하는 Idempotency-Key 헤더와 상태 머신 설계 누락.
4. 비표준 에러 규격과 비효율적 페이징: 대용량 트래픽에서 심각한 DB 부하를 주는 오프셋 페이징(offset=100000)을 제시하고, 에러 응답 형식이 엔드포인트마다 제각각이어 클라이언트 에러 핸들러를 파편화시킴.
• 해결 목표:
• Principal API Governance Architect 페르소나 주입: 리소스 계층 구조, HTTP 동사 의미론(Semantics), RFC 7807/9457 에러 표준, RFC 7231 캐싱 규칙을 엄격히 준수.
• 엔터프라이즈 레벨의 안정성 확보: 결제/주문 등 금융성 트랜잭션의 멱등성 헤더 처리, 커서 기반 페이지네이션, 필터링/정렬 파라미터 표준화.
• 도메인 목적별 듀얼 레시피 체계화: 동기식 REST API 수명주기 설계(레시피 B)와 마이크로서비스 간 비동기 이벤트 통지를 위한 웹훅 & DLQ 아키텍처 설계(레시피 C)의 이원화.

2. 프롬프트 레시피 비교

❌ 레시피 A: 기본형 (단순 질의 초안)

역할 정의, 리소스 모델링 규칙, 표준 프로토콜 명세 없이 단순 엔드포인트 도출만 요청하는 초안입니다.

[영문 프롬프트 (복사용)]

Design REST API for: [service]

[한글 프롬프트 (1:1 매칭 복사용)]

다음 서비스에 대한 REST API를 설계해줘: [서비스명]
💡 [프롬프트 구조 및 핵심 파라미터 해설란]
• 구조적 결함: 리소스 모델링 기준, HTTP 메서드 매핑 원칙, 에러 명세 표준, 멱등성 보장 지침이 전혀 정의되어 있지 않습니다.
• 부작용: AI는 /createOrder, /getOrderById, /cancelOrder와 같은 동사 위주의 잘못된 URI를 생성하며, 모든 비즈니스 예외 상황에 200 OK를 내려주는 비표준 명세를 작성합니다.

⚡ 레시피 B: Principal API Governance Architect 실무 종결형 REST API 설계 프롬프트

리소스 중심 모델링, HTTP 상태 코드 매핑, RFC 7807 표준 에러 스키마, 멱등성 헤더, 커서 페이지네이션을 완벽하게 적용하여 프로덕션 백엔드에 즉시 투입 가능한 명세를 도출하는 레시피입니다.

[영문 프롬프트 (복사용)]

You are a Principal API Governance Architect and Staff Backend Tech Lead. Design a strict, production-grade RESTful API specification for the target domain service described below. ### Target Domain & Requirements: - Business Domain: [e.g., B2B E-Commerce Order & Payment Lifecycle] - Core Entities: [e.g., Order, OrderItem, Payment, Refund, Customer] - High-Traffic / Critical Operations: [e.g., Order Placement, Idempotent Payment Authorization, Partial Refund, Cursor Search] ### API Design Directives: 1. RESOURCE MODELING & URI SEMANTICS: - Enforce plural nouns for collections (e.g., /v1/orders, /v1/orders/{orderId}/payments). Strictly prohibit verbs in URIs. - Use standard HTTP verbs (GET, POST, PUT, PATCH, DELETE) mapped accurately to idempotent and safe semantics. 2. IDEMPOTENCY & SAFETY: - Specify `Idempotency-Key` header mechanism (UUIDv4, 24h TTL) for financial/state-changing POST endpoints. 3. ERROR SPECIFICATION (RFC 7807 / RFC 9457): - Every 4xx/5xx response must follow Problem Details format: type, title, status, detail, instance, invalid_params. 4. PAGINATION & FILTERING: - Provide opaque cursor-based pagination (cursor, limit) and ISO-8601 timestamp filtering. 5. OPENAPI 3.1 COMPLIANT OUTPUT: - Provide concrete endpoint signatures, request JSON schemas, response status codes (200, 201, 400, 409, 422), and header specifications.

[한글 프롬프트 (1:1 매칭 복사용)]

당신은 수석 API 거버넌스 아키텍트(Principal API Governance Architect)이자 스태프 백엔드 테크 리드(Staff Backend Tech Lead)입니다. 아래에 설명된 대상 도메인 서비스에 대해 엄격하고 프로덕션 수준의 표준 RESTful API 명세를 설계하십시오. ### 대상 도메인 및 요구사항: - 비즈니스 도메인: [예: B2B 전자상거래 주문 및 결제 수명주기] - 핵심 엔티티: [예: 주문(Order), 주문항목(OrderItem), 결제(Payment), 환불(Refund), 고객(Customer)] - 대규모 트래픽 및 주요 작업: [예: 주문 생성, 멱등 결제 승인, 부분 환불, 커서 기반 검색] ### API 설계 필수 지침: 1. 리소스 모델링 및 URI 의미론: - 컬렉션 리소스는 반드시 복수형 명사(/v1/orders, /v1/orders/{orderId}/payments)를 사용하십시오. URI 경로 내 동사 사용을 전면 금지합니다. - 안전성(Safe) 및 멱등성(Idempotent) 의미론에 맞춰 표준 HTTP 동사(GET, POST, PUT, PATCH, DELETE)를 정확하게 매핑하십시오. 2. 멱등성 및 안전성 보장: - 결제 및 상태 변경 POST 엔드포인트에 `Idempotency-Key` 헤더(UUIDv4, 24시간 TTL 캐시) 처리 메커니즘을 명시하십시오. 3. RFC 7807 / RFC 9457 표준 에러 규격: - 모든 4xx 및 5xx 에러 응답은 Problem Details 형식(type, title, status, detail, instance, invalid_params)을 엄격히 준수하십시오. 4. 페이지네이션 및 필터링: - 대규모 조회를 위해 불투명 커서 기반 페이지네이션(cursor, limit) 및 ISO-8601 타임스탬프 필터링 규격을 정의하십시오. 5. OpenAPI 3.1 호환 명세 도출: - 구체적인 엔드포인트 시그니처, 요청 JSON 스키마, 상태 코드(200, 201, 400, 409, 422), 헤더 규격을 작성하십시오.
💡 [프롬프트 구조 및 핵심 파라미터 해설란]
• 명사 복수형 강제 및 동사 배제: /orders/{id}/cancel 대신 /orders/{id}에 PATCH 또는 취소 전용 서브리소스 전이를 명시하도록 유도하여 REST 원칙을 완벽히 지킵니다.
• RFC 7807 Problem Details 강제: 클라이언트가 에러 상태 코드와 비즈니스 세부 오류를 파싱할 수 있는 표준화된 JSON 에러 스키마를 확립합니다.
• Idempotency-Key: 결제/주문 등 중복 호출이 치명적인 구간에 필수적인 멱등성 키 스펙을 사전에 정의합니다.

🚀 레시피 C: 마이크로서비스 간 비동기 웹훅 & 이벤트 API 설계 특화 (Webhook & Event Architect)

시스템 간 실시간 데이터 통지, HMAC-SHA256 서명 보안, 지수 백오프 재시도 및 DLQ(Dead Letter Queue)를 포함하는 엔터프라이즈 웹훅 API 전용 레시피입니다.

[영문 프롬프트 (복사용)]

You are an Enterprise Integration & Event-Driven API Architect. Design a highly reliable, secure Webhook & Asynchronous Event Notification API system for external consumer integration. ### Target Event System: - Domain & Publisher: [e.g., Global Logistics & Fulfillment Service] - Target Event Triggers: [e.g., delivery.dispatched, delivery.delayed, delivery.completed] - Consumer Environment: [e.g., B2B Merchant SaaS, Third-party ERPs] ### Webhook Architecture Directives: 1. WEBHOOK CATALOG & EVENT ENVELOPE: - Design CloudEvents 1.0 compatible JSON payload: id, source, specversion, type, time, datacontenttype, data. 2. SECURITY & AUTHENTICATION: - Implement HMAC-SHA256 signature verification header (`X-Hub-Signature-256` or `X-Signature-SHA256`). - Include timestamp header (`X-Signature-Timestamp`) to prevent replay attacks (tolerance: 300s). 3. DELIVERY RELIABILITY & RETRY POLICY: - Define exponential backoff retry schedule (e.g., 5s, 30s, 5m, 30m, 2h) with Dead Letter Queue (DLQ) storage after max attempts. 4. CONSUMER SUBSCRIPTION MANAGEMENT API: - Provide REST endpoints for consumers to register, rotate secret, test-ping, and query delivery event logs.

[한글 프롬프트 (1:1 매칭 복사용)]

당신은 엔터프라이즈 통합 및 이벤트 기반 API 아키텍트(Enterprise Integration & Event-Driven API Architect)입니다. 외부 파트너 및 연동 시스템을 위한 신뢰성 높고 안전한 웹훅(Webhook) 및 비동기 이벤트 알림 API 시스템을 설계하십시오. ### 대상 이벤트 시스템: - 도메인 및 발행자: [예: 글로벌 풀필먼트 및 물류 배송 서비스] - 이벤트 트리거: [예: delivery.dispatched, delivery.delayed, delivery.completed] - 수신자(컨슈머) 환경: [예: B2B 쇼핑몰 SaaS, 외부 ERP 시스템] ### 웹훅 아키텍처 필수 지침: 1. 웹훅 카탈로그 및 이벤트 엔벨로프: - CloudEvents 1.0 호환 JSON 페이로드 구조(id, source, specversion, type, time, datacontenttype, data)를 정의하십시오. 2. 보안 및 인증 메커니즘: - HMAC-SHA256 기반 서명 검증 헤더(`X-Signature-SHA256`)를 설계하십시오. - 재생 공격(Replay Attack) 방지를 위한 타임스탬프 헤더(`X-Signature-Timestamp`, 300초 만료 검증)를 포함하십시오. 3. 전송 신뢰성 및 재시도 정책: - 지수 백오프(Exponential Backoff) 재시도 주기(예: 5초, 30초, 5분, 30분, 2시간)와 최종 실패 시 DLQ(Dead Letter Queue) 적재 정책을 정의하십시오. 4. 수신자 구독 관리 REST API: - 웹훅 URL 등록, 시크릿 키 재발급(Rotation), 테스트 핑(Ping), 전송 이력 조회를 위한 REST 엔드포인트를 설계하십시오.
💡 [프롬프트 구조 및 핵심 파라미터 해설란]
• CloudEvents 1.0 준수: 이벤트 페이로드에 표준 메타데이터 엔벨로프를 감싸 마이크로서비스 간 이벤트 역직렬화 호환성을 보장합니다.
• HMAC-SHA256 + 타임스탬프: 중간자 공격과 재생 공격을 원천 차단하는 엔터프라이즈 웹훅 보안의 필수 요건을 강제합니다.
• 재시도 및 DLQ 정책: 수신자 서버 다운 시의 무한 재전송으로 인한 발행자 스레드 고갈을 방지하고 실패 이벤트를 보존합니다.

3. 실행 결과 비교: 레시피 A vs B vs C 실전 대조

❌ 레시피 A 실행 결과: [단순 질의의 한계] URI에 동사 남발(/getUser, /deleteItem) 및 HTTP 메서드 오용

전자상거래 주문 시스템에 대해 레시피 A를 실행했을 때 도출되는 전형적인 결과입니다.

전자상거래 주문 시스템을 위한 API 명세입니다: 1. 주문 생성: POST /api/createOrder 2. 주문 조회: GET /api/getOrder?orderId=123 3. 주문 상태 변경: POST /api/changeOrderStatus 4. 주문 삭제: POST /api/deleteOrder 성공 응답: { "code": 200, "message": "success", "data": { ... } } 실패 응답: { "code": 200, "message": "잔액 부족", "data": null }
❌ [문제점 및 한계 분석]
• /createOrder, /getOrder처럼 URI에 불필요한 동사가 들어가 REST 원칙을 정면으로 위배합니다.
• 실패 상황임에도 HTTP 응답 코드로 200 OK를 내보내 API 게이트웨이나 모니터링 시스템에서 에러율을 감지할 수 없습니다.
• 네트워크 재시도 시 중복 주문을 방지할 수 있는 멱등성 키나 트랜잭션 안전장치가 전혀 없습니다.

⚡ 레시피 B 실행 결과: [실전 사례 1] B2B 전자상거래 주문·결제 수명주기 REST API 명세 (POST /v1/orders, POST /v1/orders/{id}/payments, 멱등성 보장, 201/409/422 RFC 7807 스키마, 후속 체이닝 2종, 4단계 워크플로우)

레시피 B를 통해 도출된 완벽한 프로덕션급 B2B 주문 및 결제 API 명세입니다.

엔드포인트 명세 및 요청/응답 스키마

1. 주문 생성 (POST /v1/orders)

• 요청 헤더: Idempotency-Key: 7b8b2e3d-4c5f-4a1e-8e92-3a5e8c1b9d44, Content-Type: application/json
• 요청 페이로드:
{ "customerId": "cust_9942a1b8", "currency": "KRW", "items": [ { "productId": "prod_101", "quantity": 2, "unitPrice": 45000 }, { "productId": "prod_204", "quantity": 1, "unitPrice": 120000 } ], "shippingAddress": { "recipient": "홍길동", "postalCode": "06234", "street": "서울특별시 강남구 테헤란로 152" } }
• 성공 응답 (HTTP 201 Created):
• 헤더: Location: /v1/orders/ord_20261022_8841a
{ "id": "ord_20261022_8841a", "status": "PENDING_PAYMENT", "totalAmount": 210000, "currency": "KRW", "createdAt": "2026-10-22T08:30:00Z" }

2. 멱등 결제 승인 (POST /v1/orders/{orderId}/payments)

• 멱등성 충돌 시 에러 응답 (HTTP 409 Conflict - RFC 7807 Schema):
{ "type": "https://api.example.com/errors/idempotency-conflict", "title": "Idempotency Key Conflict", "status": 409, "detail": "Provided Idempotency-Key is currently being processed by another transaction.", "instance": "/v1/orders/ord_20261022_8841a/payments", "code": "CONCURRENT_REQUEST_IN_FLIGHT" }

3. 유효성 검증 실패 응답 (HTTP 422 Unprocessable Entity - RFC 9457 Schema):

{ "type": "https://api.example.com/errors/validation-failed", "title": "Invalid Request Parameters", "status": 422, "detail": "The payload failed business semantic validation.", "instance": "/v1/orders", "invalid_params": [ { "name": "items[0].quantity", "reason": "Quantity must be greater than zero." }, { "name": "shippingAddress.postalCode", "reason": "Invalid 5-digit postal code format." } ] }

후속 체이닝 2종

체이닝 1 (OpenAPI 3.1 YAML 변환): "위에서 작성한 /v1/orders 및 /v1/orders/{orderId}/payments 엔드포인트 명세를 Swagger UI 및 Redoc 렌더링이 가능한 OpenAPI 3.1 YAML 파일 전문으로 변환해줘."
체이닝 2 (Redis 기반 Idempotency 미들웨어 구현): "Idempotency-Key 헤더를 수신하여 Redis SET key value NX EX 86400 명령어로 동시 요청을 409로 락킹하고 이전 결과를 캐싱 반환하는 Express/NestJS 미들웨어 코드를 작성해줘."

4단계 워크플로우

1. 도메인 엔티티 및 리소스 식별: 시스템 명사를 추출하여 컬렉션/단일 리소스 계층 수립.
2. HTTP 동사 및 멱등성 매핑: GET(조회/Safe), POST(생성/Non-idempotent + Key 보강), PATCH(부분수정).
3. 표준 에러 규격(RFC 7807) 일원화: 모든 예외 상황을 일관된 구조화 필드로 규격화.
4. 커서 페이지네이션 및 캐싱 헤더 정의: ETag 및 Cache-Control을 결합한 고효율 조회 파이프라인 완성.

🚀 레시피 C 실행 결과: [실전 사례 2] 실시간 물류 배송 상태 알림 웹훅 이벤트 API 명세 (POST /v1/webhooks/deliveries, HMAC 서명 검증, 후속 체이닝, 3단계 워크플로우)

글로벌 풀필먼트 서비스에서 외부 B2B 고객사로 실시간 배송 상태 변경 이벤트를 전달하는 웹훅 API 명세입니다.

AI 생성 출력 결과 (CloudEvents 1.0 JSON 페이로드)

• HTTP 요청 시그니처: POST https://partner.example.com/webhooks/delivery-events
• 전송 HTTP 헤더:
• X-Signature-SHA256: 3a2c5f1b9d4e... (HMAC-SHA256 서명값)
• X-Signature-Timestamp: 1774254600 (재생 공격 방지용 타임스탬프)
• Content-Type: application/cloudevents+json; charset=UTF-8
{ "specversion": "1.0", "id": "evt_dlv_992104fa", "source": "https://fulfillment.logistics.io/services/delivery-tracker", "type": "io.logistics.delivery.delayed", "time": "2026-10-22T08:30:00Z", "datacontenttype": "application/json", "data": { "trackingNumber": "TRK-2026-89102", "orderId": "ord_20261022_8841a", "previousStatus": "IN_TRANSIT", "currentStatus": "DELAYED", "reason": "SEVERE_WEATHER_CONDITIONS", "estimatedDelivery": "2026-10-24T18:00:00Z", "hubLocation": "ICN_HUB_03" } }

HMAC-SHA256 서명 검증 코드 예시 (Node.js)

import crypto from 'crypto'; export function verifyWebhookSignature(payloadString, signatureHeader, timestampHeader, secret) { // 1. 재생 공격(Replay Attack) 방지: 300초(5분) 이상 경과한 요청 폐기 const currentTime = Math.floor(Date.now() / 1000); if (Math.abs(currentTime - parseInt(timestampHeader, 10)) > 300) { return false; } // 2. 타임스탬프와 본문을 연결하여 서명 비교 (타임스탬프 변조 차단) const signedPayload = `${timestampHeader}.${payloadString}`; const computedSignature = crypto .createHmac('sha256', secret) .update(signedPayload, 'utf8') .digest('hex'); // 3. 타이밍 공격(Timing Attack) 방지를 위한 timingSafeEqual 사용 return crypto.timingSafeEqual( Buffer.from(signatureHeader, 'hex'), Buffer.from(computedSignature, 'hex') ); }

후속 체이닝: 재시도 큐 및 DLQ 관리자 API

추가 프롬프트: "위 웹훅 전송 실패 시 BullMQ(Redis)를 활용해 5회(5s, 30s, 5m, 30m, 2h) 지수 백오프로 재시도하고, 최종 실패한 이벤트를 DLQ 테이블에 적재 후 관리자가 수동 재전송할 수 있는 /v1/admin/webhooks/dlq/{eventId}/retry 엔드포인트를 구현해줘."

3단계 워크플로우

1. 표준 이벤트 엔벨로프 규격화: CloudEvents 규격을 통해 도메인 데이터와 메타데이터 분리.
2. HMAC 및 시간 기반 암호화 가드: 시크릿 키 기반 다이제스트 검증으로 위변조 차단.
3. 지수 백오프 및 DLQ 격리: 수신 서버 장애 시 시스템 복원력 확보.

4. REST API 아키텍처 설계를 위한 4대 엔지니어링 원칙

1. 명사 중심의 리소스 지향 설계(Resource-Oriented Design):
• URI는 시스템 내의 객체(명사 복수형)만을 가리키며, 행위는 HTTP 메서드(GET, POST, PUT, PATCH, DELETE)로 명시해야 합니다.
2. RFC 7807 / RFC 9457 표준 에러 규격 준수:
• HTTP 200에 에러를 은닉하는 안티패턴을 배제하고, type, title, status, detail, instance를 갖춘 Problem Details를 사용하여 클라이언트 오류 처리를 정형화합니다.
3. 분산 시스템의 멱등성(Idempotency) 보장:
• 네트워크 타임아웃으로 인한 클라이언트 재시도 시 결제나 주문이 이중 실행되지 않도록 Idempotency-Key 헤더와 유일성 캐시 락을 강제합니다.
4. 오프셋 대신 커서 기반 페이지네이션 채택:
• 대규모 테이블에서 OFFSET 100000은 Full Table Scan을 유발합니다. 고성능 조회를 위해 고유 정렬 키를 활용한 불투명 커서(cursor=eyJpZCI6MTAxfQ==) 방식을 기본으로 채택합니다.

5. 실전 투입용 플레이스홀더 템플릿

[역할 정의] 당신은 엔터프라이즈 RESTful API 거버넌스를 총괄하는 Principal API Architect입니다. [대상 비즈니스 도메인] - 서비스 영역: [예: 클라우드 SaaS 구독 및 청구 시스템] - 주요 리소스: [예: Subscriptions, Invoices, BillingProfiles, Plans] - 기술 환경: [예: Spring Boot 3.3 / FastAPI / Node.js NestJS] [API 설계 요구사항] 1. 리소스 모델링: 복수형 명사 컬렉션 구조와 HTTP 동사 매핑 규칙을 철저히 적용할 것. 2. 멱등성 명세: 청구 결제 등 민감 POST 요청에 대한 Idempotency-Key 처리 규격을 정의할 것. 3. 에러 처리: RFC 7807 Problem Details 형식을 따르는 400, 401, 403, 404, 409, 422 상태 코드 응답 스키마를 도출할 것. 4. 페이지네이션: 커서 기반 페이지네이션 파라미터(cursor, limit) 및 JSON 응답 구조(next_cursor, has_more)를 정의할 것. 5. 엔드포인트 명세: HTTP Method, URI 경로, 요청 바디, 응답 코드 및 바디 JSON을 명확히 제시할 것.

6. 프롬프트 활용 팁 & 주의사항

1. URI 깊이(Nesting Depth) 제한:
• /departments/{did}/teams/{tid}/projects/{pid}/tasks/{taskid}처럼 3단계 이상 중첩된 URI는 유지보수를 어렵게 만듭니다. 최상위 리소스(/tasks/{taskid})로 평탄화(Flattening)하도록 프롬프트에 '최대 2단계 깊이 제한' 조건을 추가하십시오.
2. 부분 수정(PATCH) 시 JSON Merge Patch(RFC 7396) 명시:
• 전체 수정을 의미하는 PUT 대신 일부 필드만 변경할 때는 PATCH와 함께 application/merge-patch+json 규격을 적용하도록 유도하여 엔티티 누락 버그를 방지하십시오.
3. 민감 정보의 URI 쿼리 파라미터 노출 금지:
• 인증 토큰, 주민등록번호, 계좌번호 등이 GET 요청의 쿼리 파라미터에 포함되면 웹 서버 액세스 로그나 브라우저 히스토리에 평문 노출됩니다. 민감 데이터는 반드시 요청 헤더나 POST 바디로 전송하도록 검증하십시오.
4. API 버저닝 전략 명확화:
• URI 버저닝(/v1/orders), 헤더 버저닝(Accept: application/vnd.company.v1+json), 쿼리 파라미터 버저닝 중 사내 표준에 맞는 버저닝 전략을 프롬프트 초기 제약 조건으로 전달하십시오.

댓글 쓰기

다음 이전