ストーリー
高橋アーキテクトが最後の課題を出した。
総合演習の概要
ECサイト「ShopFlow」のAPIを設計します。5つのパートで、L2 Month 2の全スキルを実践します。
| パート | テーマ | 時間 | 使うスキル |
|---|---|---|---|
| Part 1 | リソース設計とREST API | 20分 | REST設計、URI設計 |
| Part 2 | OpenAPI仕様書 | 20分 | OpenAPI 3.1 |
| Part 3 | 認証・認可とエラー設計 | 15分 | JWT、RBAC、エラーハンドリング |
| Part 4 | GraphQLスキーマ | 20分 | GraphQL、DataLoader |
| Part 5 | バージョニングとBFF | 15分 | バージョニング、BFF |
ShopFlow の要件
ECサイト ShopFlow の機能:
- ユーザー登録・ログイン
- 商品の閲覧・検索
- カートへの追加・変更・削除
- 注文の作成・確認・キャンセル
- レビューの投稿・閲覧
- お気に入り登録
- 管理者による商品管理
クライアント:
- Webアプリ(デスクトップ/タブレット)
- モバイルアプリ(iOS/Android)
- 管理画面(管理者用)
Part 1: リソース設計とREST API(20分)
要件
- ShopFlow のリソースを洗い出す
- 主要なエンドポイント(最低20個)を設計する
- 各エンドポイントの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仕様を定義してください。
GET /api/v1/products- 商品一覧(フィルタ・ソート・ページネーション)POST /api/v1/orders- 注文作成- エラーレスポンスの共通定義
解答
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分)
要件
- 3つのロール(customer, seller, admin)の権限マトリクスを定義
- 主要なエラーケースのレスポンスを設計
解答
// 権限マトリクス
// | 操作 | 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 2 | OpenAPI仕様書 | [ ] |
| Part 3 | 認証・認可とエラー設計 | [ ] |
| Part 4 | GraphQLスキーマ | [ ] |
| Part 5 | バージョニングとBFF | [ ] |
まとめ
| ポイント | 内容 |
|---|---|
| REST API設計 | リソース指向、適切なHTTPメソッドとステータスコード |
| OpenAPI | 仕様書による契約の明文化 |
| 認証・認可 | ロールに基づく権限管理 |
| GraphQL | クライアント最適化されたデータ取得 |
| バージョニング | 変化に強いAPI進化戦略 |
チェックリスト
- ECサイトのリソースを洗い出し、RESTful APIを設計できた
- OpenAPI仕様書を書いて契約を定義できた
- 認証・認可とビジネスエラーを設計できた
- GraphQLスキーマを設計し、RESTとの使い分けを判断できた
- バージョニング戦略とBFF構成を設計できた
次のステップへ
お疲れさまでした。API設計の全スキルを総動員した総合演習を完了しました。
最後に、卒業クイズに挑戦しましょう。
推定所要時間: 90分