EXERCISE 60分

ストーリー

高橋アーキテクト
理論は十分だ。実際にOpenAPI仕様書を書いてみよう

高橋アーキテクトが要件を渡した。

高橋アーキテクト
Step 2の演習で設計したTaskFlow APIの一部を、OpenAPI仕様書として定義してほしい
あなた
YAML で書くんですよね?
高橋アーキテクト
そうだ。4つのミッションに分けている。基本構造から始めて、最終的には完全な仕様書を作り上げてくれ

ミッション概要

ミッションテーマ難易度
Mission 1基本構造とinfo/servers初級
Mission 2タスクCRUDのpaths定義中級
Mission 3componentsスキーマ定義中級
Mission 4エラーレスポンスとセキュリティ上級

Mission 1: 基本構造の作成(10分)

OpenAPI仕様書の骨格(info, servers, tags)を作成してください。

要件

  • タイトル: TaskFlow API
  • バージョン: 1.0.0
  • サーバー: 本番、ステージング、ローカル
  • タグ: auth, users, projects, tasks, comments
解答
openapi: "3.1.0"
info:
  title: "TaskFlow API"
  description: |
    タスク管理アプリケーション TaskFlow のREST API仕様書。
    プロジェクトごとにタスクを管理し、チームでのコラボレーションを実現します。
  version: "1.0.0"
  contact:
    name: "TaskFlow API Team"
    email: "api-team@taskflow.example.com"
  license:
    name: "MIT"
    url: "https://opensource.org/licenses/MIT"

servers:
  - url: "https://api.taskflow.example.com/v1"
    description: "本番環境"
  - url: "https://staging-api.taskflow.example.com/v1"
    description: "ステージング環境"
  - url: "http://localhost:3000/v1"
    description: "ローカル開発環境"

tags:
  - name: auth
    description: "認証関連のAPI"
  - name: users
    description: "ユーザー管理API"
  - name: projects
    description: "プロジェクト管理API"
  - name: tasks
    description: "タスク管理API"
  - name: comments
    description: "コメントAPI"

paths: {}
components: {}

Mission 2: タスクCRUDのpaths定義(20分)

タスクの作成、一覧取得、詳細取得、更新、削除のエンドポイントをpathsに定義してください。

要件

  • POST /projects/{projectId}/tasks - タスク作成
  • GET /projects/{projectId}/tasks - タスク一覧(ページネーション、フィルタ付き)
  • GET /tasks/{taskId} - タスク詳細
  • PATCH /tasks/{taskId} - タスク更新
  • DELETE /tasks/{taskId} - タスク削除
解答
paths:
  /projects/{projectId}/tasks:
    get:
      tags: [tasks]
      summary: "タスク一覧取得"
      description: "指定プロジェクトのタスク一覧を取得します"
      operationId: "listTasks"
      parameters:
        - name: projectId
          in: path
          required: true
          schema:
            type: string
          example: "proj_123"
        - name: status
          in: query
          description: "ステータスで絞り込み(カンマ区切りで複数指定可)"
          schema:
            type: string
          example: "todo,in_progress"
        - name: assigneeId
          in: query
          description: "担当者IDで絞り込み"
          schema:
            type: string
        - name: sort
          in: query
          description: "ソート(-で降順)"
          schema:
            type: string
            default: "-createdAt"
          example: "-priority"
        - name: page
          in: query
          schema:
            type: integer
            default: 1
            minimum: 1
        - name: perPage
          in: query
          schema:
            type: integer
            default: 20
            minimum: 1
            maximum: 100
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaskListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
      security:
        - bearerAuth: []

    post:
      tags: [tasks]
      summary: "タスク作成"
      description: "指定プロジェクトにタスクを作成します"
      operationId: "createTask"
      parameters:
        - name: projectId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateTaskRequest"
      responses:
        "201":
          description: "作成成功"
          headers:
            Location:
              description: "作成されたタスクのURI"
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaskResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          $ref: "#/components/responses/ValidationError"
      security:
        - bearerAuth: []

  /tasks/{taskId}:
    get:
      tags: [tasks]
      summary: "タスク詳細取得"
      operationId: "getTask"
      parameters:
        - name: taskId
          in: path
          required: true
          schema:
            type: string
          example: "tsk_789"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaskDetailResponse"
        "404":
          $ref: "#/components/responses/NotFound"
      security:
        - bearerAuth: []

    patch:
      tags: [tasks]
      summary: "タスク更新"
      operationId: "updateTask"
      parameters:
        - name: taskId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateTaskRequest"
      responses:
        "200":
          description: "更新成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaskResponse"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationError"
      security:
        - bearerAuth: []

    delete:
      tags: [tasks]
      summary: "タスク削除"
      operationId: "deleteTask"
      parameters:
        - name: taskId
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: "削除成功"
        "404":
          $ref: "#/components/responses/NotFound"
      security:
        - bearerAuth: []

Mission 3: componentsスキーマ定義(20分)

タスク関連のスキーマを components/schemas に定義してください。

要件

  • Task(レスポンス用)
  • CreateTaskRequest(作成リクエスト)
  • UpdateTaskRequest(更新リクエスト)
  • TaskListResponse(一覧レスポンス)
  • PaginationMeta
解答
components:
  schemas:
    Task:
      type: object
      required: [id, title, status, priority, createdAt, updatedAt]
      properties:
        id:
          type: string
          example: "tsk_789"
        title:
          type: string
          example: "ログイン画面のUI改善"
        description:
          type: string
          nullable: true
          example: "モバイル対応のレスポンシブデザインに修正"
        status:
          type: string
          enum: [todo, in_progress, in_review, done]
          example: "todo"
        priority:
          type: string
          enum: [low, medium, high, critical]
          example: "high"
        assignee:
          $ref: "#/components/schemas/UserSummary"
        labels:
          type: array
          items:
            $ref: "#/components/schemas/Label"
        dueDate:
          type: string
          format: date-time
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    UserSummary:
      type: object
      required: [id, name]
      properties:
        id:
          type: string
          example: "usr_456"
        name:
          type: string
          example: "鈴木花子"

    Label:
      type: object
      required: [id, name, color]
      properties:
        id:
          type: string
          example: "lbl_001"
        name:
          type: string
          example: "UI"
        color:
          type: string
          pattern: "^#[0-9a-fA-F]{6}$"
          example: "#3b82f6"

    CreateTaskRequest:
      type: object
      required: [title]
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
        description:
          type: string
          maxLength: 5000
        assigneeId:
          type: string
        priority:
          type: string
          enum: [low, medium, high, critical]
          default: medium
        dueDate:
          type: string
          format: date-time
        labelIds:
          type: array
          items:
            type: string
          maxItems: 10

    UpdateTaskRequest:
      type: object
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
        description:
          type: string
          maxLength: 5000
          nullable: true
        status:
          type: string
          enum: [todo, in_progress, in_review, done]
        priority:
          type: string
          enum: [low, medium, high, critical]
        assigneeId:
          type: string
          nullable: true
        dueDate:
          type: string
          format: date-time
          nullable: true
        labelIds:
          type: array
          items:
            type: string
          maxItems: 10

    TaskResponse:
      type: object
      properties:
        data:
          $ref: "#/components/schemas/Task"

    TaskDetailResponse:
      type: object
      properties:
        data:
          allOf:
            - $ref: "#/components/schemas/Task"
            - type: object
              properties:
                project:
                  $ref: "#/components/schemas/ProjectSummary"
                commentCount:
                  type: integer
                  example: 5

    ProjectSummary:
      type: object
      required: [id, name]
      properties:
        id:
          type: string
          example: "proj_123"
        name:
          type: string
          example: "TaskFlow v2"

    TaskListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Task"
        meta:
          $ref: "#/components/schemas/PaginationMeta"

    PaginationMeta:
      type: object
      properties:
        totalCount:
          type: integer
          example: 42
        currentPage:
          type: integer
          example: 1
        perPage:
          type: integer
          example: 20
        totalPages:
          type: integer
          example: 3

Mission 4: エラーレスポンスとセキュリティ(10分)

エラーレスポンスの共通定義とセキュリティスキームを定義してください。

解答
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: "JWT Bearer Token による認証"

  responses:
    Unauthorized:
      description: "認証エラー"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: "UNAUTHENTICATED"
              message: "認証が必要です"
              traceId: "req_abc123"

    Forbidden:
      description: "権限エラー"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: "FORBIDDEN"
              message: "この操作を行う権限がありません"
              traceId: "req_def456"

    NotFound:
      description: "リソースが見つかりません"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: "NOT_FOUND"
              message: "指定されたリソースが見つかりません"
              traceId: "req_ghi789"

    ValidationError:
      description: "バリデーションエラー"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ValidationErrorResponse"
          example:
            error:
              code: "VALIDATION_ERROR"
              message: "入力内容に問題があります"
              details:
                - field: "title"
                  message: "タイトルは必須です"
              traceId: "req_jkl012"

  schemas:
    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message, traceId]
          properties:
            code:
              type: string
            message:
              type: string
            traceId:
              type: string

    ValidationErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message, details, traceId]
          properties:
            code:
              type: string
            message:
              type: string
            details:
              type: array
              items:
                type: object
                properties:
                  field:
                    type: string
                  message:
                    type: string
            traceId:
              type: string

security:
  - bearerAuth: []

達成度チェック

ミッションテーマ完了
Mission 1基本構造[ ]
Mission 2パス定義[ ]
Mission 3スキーマ定義[ ]
Mission 4エラーとセキュリティ[ ]

まとめ

ポイント内容
基本構造openapi, info, servers, tags で全体を定義
pathsHTTPメソッドごとにparameters, requestBody, responsesを記述
componentsschemas, responses, securitySchemes で再利用可能な定義
$refコンポーネントへの参照で重複を排除

チェックリスト

  • OpenAPI仕様書の基本構造を自分で書ける
  • CRUDエンドポイントのpaths定義ができる
  • データモデルのスキーマ定義ができる
  • エラーレスポンスとセキュリティの定義ができる

次のステップへ

お疲れさまでした。OpenAPI仕様書を実際に書く練習をしました。

次はStep 3のチェックポイントです。


推定所要時間: 60分