EXERCISE 90分

ストーリー

高橋アーキテクトが最後の課題を出した。

高橋アーキテクト
ここまでの全てを使って、ECサイトのAPIを設計してもらう。REST API設計、OpenAPI仕様、GraphQLスキーマ、バージョニング戦略。全てを自分の手で設計するんだ
あなた
一人で全部やるんですか?
高橋アーキテクト
そうだ。これができたら、君は”API設計者”だ

総合演習の概要

ECサイト「ShopFlow」のAPIを設計します。5つのパートで、L2 Month 2の全スキルを実践します。

パートテーマ時間使うスキル
Part 1リソース設計とREST API20分REST設計、URI設計
Part 2OpenAPI仕様書20分OpenAPI 3.1
Part 3認証・認可とエラー設計15分JWT、RBAC、エラーハンドリング
Part 4GraphQLスキーマ20分GraphQL、DataLoader
Part 5バージョニングとBFF15分バージョニング、BFF

ShopFlow の要件

ECサイト ShopFlow の機能:
- ユーザー登録・ログイン
- 商品の閲覧・検索
- カートへの追加・変更・削除
- 注文の作成・確認・キャンセル
- レビューの投稿・閲覧
- お気に入り登録
- 管理者による商品管理

クライアント:
- Webアプリ(デスクトップ/タブレット)
- モバイルアプリ(iOS/Android)
- 管理画面(管理者用)

Part 1: リソース設計とREST API(20分)

要件

  1. ShopFlow のリソースを洗い出す
  2. 主要なエンドポイント(最低20個)を設計する
  3. 各エンドポイントのHTTPメソッドとパスを定義する
解答
// === リソース一覧 ===
// Users, Products, Categories, Cart, CartItems,
// Orders, OrderItems, Reviews, Favorites

// === エンドポイント設計 ===

// 認証
POST   /api/v1/auth/register              // ユーザー登録
POST   /api/v1/auth/login                 // ログイン
POST   /api/v1/auth/refresh               // トークンリフレッシュ
POST   /api/v1/auth/logout                // ログアウト

// ユーザー
GET    /api/v1/users/me                   // 自分の情報取得
PATCH  /api/v1/users/me                   // 自分の情報更新

// 商品
GET    /api/v1/products                   // 商品一覧(検索・フィルタ・ソート)
GET    /api/v1/products/:id               // 商品詳細
GET    /api/v1/products/:id/reviews       // 商品のレビュー一覧

// カテゴリ
GET    /api/v1/categories                 // カテゴリ一覧
GET    /api/v1/categories/:id/products    // カテゴリの商品一覧

// カート
GET    /api/v1/cart                       // カート取得
POST   /api/v1/cart/items                 // 商品をカートに追加
PATCH  /api/v1/cart/items/:id             // カート内商品の数量変更
DELETE /api/v1/cart/items/:id             // カートから商品を削除
DELETE /api/v1/cart                       // カートをクリア

// 注文
GET    /api/v1/orders                     // 注文一覧
POST   /api/v1/orders                     // 注文作成(カートから)
GET    /api/v1/orders/:id                 // 注文詳細
POST   /api/v1/orders/:id/cancel          // 注文キャンセル

// レビュー
POST   /api/v1/products/:id/reviews       // レビュー投稿
PATCH  /api/v1/reviews/:id                // レビュー編集
DELETE /api/v1/reviews/:id                // レビュー削除

// お気に入り
GET    /api/v1/favorites                  // お気に入り一覧
POST   /api/v1/favorites                  // お気に入り追加
DELETE /api/v1/favorites/:productId       // お気に入り削除

// 管理者
POST   /api/v1/admin/products             // 商品登録
PUT    /api/v1/admin/products/:id         // 商品更新
DELETE /api/v1/admin/products/:id         // 商品削除

// 検索
GET    /api/v1/search?q=keyword           // 横断検索

Part 2: OpenAPI仕様書(20分)

要件

以下の3つのエンドポイントのOpenAPI仕様を定義してください。

  1. GET /api/v1/products - 商品一覧(フィルタ・ソート・ページネーション)
  2. POST /api/v1/orders - 注文作成
  3. エラーレスポンスの共通定義
解答
openapi: "3.1.0"
info:
  title: "ShopFlow API"
  version: "1.0.0"

paths:
  /products:
    get:
      tags: [products]
      summary: "商品一覧取得"
      operationId: "listProducts"
      parameters:
        - name: category
          in: query
          schema: { type: string }
        - name: minPrice
          in: query
          schema: { type: number, minimum: 0 }
        - name: maxPrice
          in: query
          schema: { type: number, minimum: 0 }
        - name: search
          in: query
          schema: { type: string }
        - name: sort
          in: query
          schema:
            type: string
            enum: [price, -price, name, -name, -createdAt]
            default: "-createdAt"
        - 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:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/ProductSummary"
                  meta:
                    $ref: "#/components/schemas/PaginationMeta"

  /orders:
    post:
      tags: [orders]
      summary: "注文作成"
      operationId: "createOrder"
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateOrderRequest"
      responses:
        "201":
          description: "注文作成成功"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Order"
        "422":
          $ref: "#/components/responses/ValidationError"

components:
  schemas:
    ProductSummary:
      type: object
      required: [id, name, price, imageUrl]
      properties:
        id: { type: string, example: "prd_001" }
        name: { type: string, example: "ワイヤレスイヤホン" }
        price: { type: number, example: 4980 }
        imageUrl: { type: string, format: uri }
        averageRating: { type: number, example: 4.5 }
        reviewCount: { type: integer, example: 128 }

    CreateOrderRequest:
      type: object
      required: [shippingAddressId, paymentMethodId]
      properties:
        shippingAddressId: { type: string }
        paymentMethodId: { type: string }
        note: { type: string, maxLength: 500 }

    Order:
      type: object
      required: [id, status, totalAmount, createdAt]
      properties:
        id: { type: string, example: "ord_789" }
        status:
          type: string
          enum: [pending, paid, shipped, delivered, cancelled]
        items:
          type: array
          items:
            $ref: "#/components/schemas/OrderItem"
        totalAmount: { type: number, example: 14940 }
        createdAt: { type: string, format: date-time }

    OrderItem:
      type: object
      properties:
        productId: { type: string }
        productName: { type: string }
        quantity: { type: integer }
        unitPrice: { type: number }
        subtotal: { type: number }

    PaginationMeta:
      type: object
      properties:
        totalCount: { type: integer }
        currentPage: { type: integer }
        perPage: { type: integer }
        totalPages: { type: integer }

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

  responses:
    ValidationError:
      description: "バリデーションエラー"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"

  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

Part 3: 認証・認可とエラー設計(15分)

要件

  1. 3つのロール(customer, seller, admin)の権限マトリクスを定義
  2. 主要なエラーケースのレスポンスを設計
解答
// 権限マトリクス
// | 操作                | customer | seller | admin |
// |---------------------|----------|--------|-------|
// | 商品閲覧             | o        | o      | o     |
// | カート操作           | o        | o      | o     |
// | 注文作成/キャンセル    | o        | o      | o     |
// | レビュー投稿          | o        | o      | o     |
// | 商品登録/編集/削除     |          | o      | o     |
// | 全ユーザーの注文閲覧   |          |        | o     |
// | ユーザー管理          |          |        | o     |

// エラーレスポンス設計
// 1. カートが空の状態で注文を作成
// 422 Unprocessable Entity
{
  "error": {
    "code": "EMPTY_CART",
    "message": "カートが空のため注文を作成できません",
    "traceId": "req_abc123"
  }
}

// 2. 在庫不足
// 409 Conflict
{
  "error": {
    "code": "INSUFFICIENT_STOCK",
    "message": "在庫が不足しています",
    "details": [
      { "field": "items[0]", "message": "「ワイヤレスイヤホン」の在庫が2個不足しています" }
    ],
    "traceId": "req_def456"
  }
}

// 3. 既にキャンセル済みの注文をキャンセル
// 409 Conflict
{
  "error": {
    "code": "ORDER_ALREADY_CANCELLED",
    "message": "この注文は既にキャンセルされています",
    "traceId": "req_ghi789"
  }
}

// 4. 発送済みの注文をキャンセル
// 422 Unprocessable Entity
{
  "error": {
    "code": "ORDER_NOT_CANCELLABLE",
    "message": "発送済みの注文はキャンセルできません。返品手続きをご利用ください。",
    "traceId": "req_jkl012"
  }
}

Part 4: GraphQLスキーマ(20分)

要件

ShopFlow の商品詳細画面に必要なGraphQLスキーマとクエリを設計してください。

解答
type Query {
  product(id: ID!): Product
  products(
    category: String
    minPrice: Float
    maxPrice: Float
    search: String
    sort: ProductSort = NEWEST
    first: Int = 20
    after: String
  ): ProductConnection!
}

type Mutation {
  addToCart(productId: ID!, quantity: Int!): AddToCartPayload!
  addReview(input: AddReviewInput!): AddReviewPayload!
  toggleFavorite(productId: ID!): ToggleFavoritePayload!
}

type Product {
  id: ID!
  name: String!
  description: String!
  price: Float!
  images: [String!]!
  category: Category!
  averageRating: Float
  reviewCount: Int!
  reviews(first: Int = 5, after: String): ReviewConnection!
  inStock: Boolean!
  stockQuantity: Int!
  isFavorited: Boolean!  # 認証ユーザーのお気に入り状態
  relatedProducts(limit: Int = 4): [Product!]!
  createdAt: DateTime!
}

enum ProductSort {
  NEWEST
  PRICE_ASC
  PRICE_DESC
  RATING
  POPULAR
}

# 商品詳細画面のクエリ
query ProductDetail($id: ID!) {
  product(id: $id) {
    id
    name
    description
    price
    images
    category { id, name }
    averageRating
    reviewCount
    inStock
    isFavorited
    reviews(first: 5) {
      edges {
        node {
          id
          rating
          comment
          author { name, avatarUrl }
          createdAt
        }
      }
      pageInfo { hasNextPage, endCursor }
    }
    relatedProducts(limit: 4) {
      id
      name
      price
      images
      averageRating
    }
  }
}
# → 1回のリクエストで商品情報、レビュー、関連商品をすべて取得

Part 5: バージョニングとBFF(15分)

要件

ShopFlow のバージョニング戦略とBFF構成を設計してください。

解答
// バージョニング戦略: URIバージョニング
// 理由: 公開APIとして外部開発者にも提供する可能性があるため、
//       最も分かりやすいURIバージョニングを採用

// BFF構成:
//
// Webアプリ    → Web BFF (REST)    → ShopFlow API
// モバイルアプリ → Mobile BFF (GraphQL) → ShopFlow API
// 管理画面     → Admin BFF (REST)   → ShopFlow API
//
// 理由:
// - Web: ページ遷移が多い、REST APIで十分
// - モバイル: 帯域節約のためGraphQL BFF
// - 管理画面: シンプルなCRUD、REST APIで十分

// Mobile BFF (GraphQL)
// 商品一覧画面(モバイル向け最適化)
query MobileProductList($category: String, $after: String) {
  products(category: $category, first: 10, after: $after) {
    edges {
      node {
        id
        name
        price
        images       # 最初の1枚だけ使用
        averageRating
      }
    }
    pageInfo { hasNextPage, endCursor }
  }
}

// 廃止ポリシー
// - 新バージョンリリース後、旧バージョンは12ヶ月間サポート
// - 同時にサポートするバージョンは最大2つ
// - 廃止3ヶ月前にレート制限を段階的に適用
// - Sunset ヘッダーで廃止日を通知

達成度チェック

パートテーマ完了
Part 1リソース設計とREST API[ ]
Part 2OpenAPI仕様書[ ]
Part 3認証・認可とエラー設計[ ]
Part 4GraphQLスキーマ[ ]
Part 5バージョニングとBFF[ ]

まとめ

ポイント内容
REST API設計リソース指向、適切なHTTPメソッドとステータスコード
OpenAPI仕様書による契約の明文化
認証・認可ロールに基づく権限管理
GraphQLクライアント最適化されたデータ取得
バージョニング変化に強いAPI進化戦略

チェックリスト

  • ECサイトのリソースを洗い出し、RESTful APIを設計できた
  • OpenAPI仕様書を書いて契約を定義できた
  • 認証・認可とビジネスエラーを設計できた
  • GraphQLスキーマを設計し、RESTとの使い分けを判断できた
  • バージョニング戦略とBFF構成を設計できた

次のステップへ

お疲れさまでした。API設計の全スキルを総動員した総合演習を完了しました。

最後に、卒業クイズに挑戦しましょう。


推定所要時間: 90分