システムアーキテクチャ設計書¶
システム概要¶
リツビ BtoB ECサイトの技術スタック全体とアーキテクチャ設計をまとめた文書です。
ローカル開発環境アーキテクチャ¶
開発環境の構成¶
ローカル開発環境では、Vendureサーバーはホストマシンで直接実行され、依存サービス(PostgreSQL、Redis等)のみがDockerコンテナで実行されます。これはVendure開発における標準的な構成です。
アーキテクチャ図¶
┌─────────────────────────────────────────────────────────────┐
│ ホストマシン (開発環境) │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Vendure Server (Node.js) - ホストで直接実行 │ │
│ │ - TypeScript watch mode (自動コンパイル) │ │
│ │ - pm2 watch (ホットリロード) │ │
│ │ - Port: 3021 │ │
│ │ - デバッガ直接アタッチ可能 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Dashboard (Vite) - ホストで直接実行 │ │
│ │ - Vite dev server │ │
│ │ - Port: 6202 (独立起動の正規経路は `/`) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Docker Compose Services │ │
│ │ │ │
│ │ - PostgreSQL (Port: 5433) │ │
│ │ - Redis (Port: 6379) ※profiles: redis (opt-in) │ │
│ │ - pgAdmin (Port: 8080) │ │
│ │ - MailCatcher (Port: 1080/1025) │ │
│ │ - Redis Commander (Port: 8081) ※profiles: redis │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
この構成を採用する理由¶
- ホットリロードの高速化:
TypeScriptのウォッチモードや
pm2 watchによる再起動が、コンテナ内よりもホストマシンで直接実行する方が高速 - デバッグの容易さ: VS Codeなどのデバッガを直接アタッチでき、ブレークポイントやプロファイル調査が容易
- 開発効率: コンテナのビルドや再起動のオーバーヘッドがなく、開発サイクルが速い
- 依存サービスの分離: データベースやキャッシュなどの依存サービスだけをDocker化することで、本番環境に近い状態を維持しつつ、アプリケーション自体は軽量に開発できる
本番環境との違い¶
| 項目 | ローカル開発環境 | 本番環境(Fly.io) |
|---|---|---|
| Vendureサーバー | ホストマシンで直接実行 | Dockerコンテナ内で実行 |
| PostgreSQL | Dockerコンテナ | 独立したFly.ioアプリ(ritsubi-postgres-db) |
| Redis | Dockerコンテナ | 本番・ステージングとも Fly.io 専用 Redis app |
| ホットリロード | TypeScript watch + pm2 watch | なし(ビルド済み) |
| デバッグ | VS Code直接アタッチ可能 | ログベース |
| メール送信 | MailCatcher(開発用) | 実際のSMTPサーバー |
| 開発ツール | MailCatcher、pgAdmin、Redis Commander | 使用しない |
詳細は
Vendure開発ガイド
を参照してください。
本番環境(Fly.io)コンテナ化アーキテクチャ¶
コンテナ化の単位¶
Fly.ioへのデプロイでは、Vendureサーバー(ritsubi-vendure-server)とその依存パッケージのみが1つのコンテナに含まれます。
ビルドコンテキストと含まれるパッケージ¶
- ビルドコンテキスト: プロジェクトルート(monorepo全体)
- 含まれるパッケージ:
ritsubi-vendure-server(メインアプリケーション)@ritsubi/plugins(すべてのカスタムプラグインを集約)@ritsubi/domain(ドメインモデル)@ritsubi/utils(ユーティリティ)@ritsubi/contract(GraphQLスキーマ)- 除外されるパッケージ:
ritsubi-storefront、その他の非依存パッケージ
マルチステージビルド¶
apps/vendure-server/Dockerfile.fly は3段階のマルチステージビルドを使用:
- baseステージ: 全ワークスペースパッケージをビルド
- deployステージ: 本番依存関係のみをインストールし、
pnpm deployで展開(依存関係をフラット化しシンボリックリンク問題を回避) - runnerステージ: 最小限のランタイムイメージにコピー
コンテナ内の構造¶
/app/apps/vendure-server/ # Vendureサーバー本体
├── dist/ # ビルド済みTypeScript
├── node_modules/ # 依存関係(pnpm deployで展開)
└── src/ # ソースコード(デバッグ用)
/app/packages/ # 依存するワークスペースパッケージ(ビルド成果物のみ)
├── plugins/
├── domain/
├── utils/
└── contract/
デプロイフロー¶
- ビルド: GitHub Actions がルートコンテキストから Docker image を build
- プッシュ:
registry.fly.ioへ image を push - デプロイ: staging / production は build 済み image を指定して展開
- スキーマ反映: Fly
release_commandが deploy 中に migration + structural repair を自動実行し、deploy-fly-app/--skip-release-commandを使う復旧時だけmigrate-flyで同じ release contract を明示再実行する - 起動: アプリケーションを起動し、必要な seed は post-deploy で個別実行
詳細は
デプロイメントガイド を参照してください。
アーキテクチャ概要¶
システム構成¶
graph TB
subgraph "Internet"
U[ユーザー]
CF[Cloudflare CDN]
end
subgraph "Cloudflare Workers (Global Edge)"
subgraph "Storefront (Vite + TanStack Router)"
NS["Worker (Static Assets / SPA Fallback / Worker API)"]
end
end
subgraph "Fly.io Tokyo Region (nrt)"
subgraph "Vendure Backend"
V1[Vendure Instance 1]
V2[Vendure Instance 2]
WK[Worker Instance]
end
subgraph "Data Layer"
PG[(Fly Postgres)]
UP[(Redis: Fly-hosted)]
VOL[Fly Volumes]
end
end
subgraph "External Services"
SMTP[メール配信]
PAY[決済ゲートウェイ]
ERP[SMILE ERP]
S3[Cloudflare R2]
end
U --> CF
CF --> NS
NS --> V1
NS --> V2
V1 --> PG
V2 --> PG
V1 --> UP
V2 --> UP
WK --> UP
WK --> PG
V1 --> VOL
V2 --> VOL
V1 --> S3
V1 --> SMTP
V2 --> PAY
V1 --> ERP
認証フロー(顧客の仮ID運用)¶
- 顧客は Storefront の初回ログイン専用画面で仮IDと仮パスワードを入力する
- 仮IDに
@ritsubi-platform.comを付与して通常ログインを流用する - Vendure 側ではメールアドレスとして扱い、管理者向けの特別フローは設けない
Storefront の公開/認証境界¶
- Storefront の正本は 会員制 catalog であり、
/、/products、/search、/products/[slug]、/campaigns/*、/cart、/checkout、/quick-order、/account/*など commerce 導線はログイン必須とする。 - 未認証でも閲覧可能な公開例外は editorial 導線 の
/articles/*と/announcements/*、および signed preview token で保護する/products/previewに限定する。 - この境界は TanStack Router の route guard
(
apps/storefront/src/runtime/auth-guard.ts) を正本として強制し、beforeLoadから適用する。未認証時は/auth/login?redirect=<元URL>へリダイレクトする。 - Worker navigation guard (
apps/storefront/worker.js) と/api/auth-sessionは、 cookie の存在だけではなく VendureactiveCustomer.idを確認する。stale cookie、 顧客削除、session drift は SPA shell を描画せず login redirect に倒す。 - 目的は、得意先別の表示制御・価格制御・購買条件に依存する route を匿名閲覧や public cache から隔離し、CMS 記事/お知らせだけを外部公開可能な面として切り出すことにある。
技術スタック一覧¶
全体構成¶
| コンポーネント | 技術/サービス | 目的 |
|---|---|---|
| Frontend | Vite 8.x + TanStack Router 1.x | React SPA Storefront + Worker API |
| Backend | Vendure 3.5 (Node.js/TypeScript) | Headless E-commerce Engine |
| Database | PostgreSQL 17 + pg_trgm (Fly.io Docker) |
メインデータストレージ |
| Cache/Queue | 本番・ステージングとも Fly.io 専用 Redis | staging も本番と同じ session/shared cache 経路 |
| Storage | Cloudflare R2 + Fly Volumes | アセット・帳票・バックアップ保管とローカルキャッシュ |
| CDN | Cloudflare | 静的アセット配信 |
| Hosting (FE) | Cloudflare Workers + Static Assets | グローバルエッジでの SPA / 静的配信 |
| Hosting (BE) | Fly.io (Tokyo Region) | コンテナオーケストレーション |
| Hosting (CMS) | Webarena Indigo (VPS) | WordPress ホスティング (Ansible + Docker) |
フロントエンド技術スタック¶
| 技術 | バージョン | 用途 |
|---|---|---|
| Vite | 8.x | フロントエンド build / dev server |
| TanStack Router | 1.x | ルーティング / route guard |
| React | 19.x | UI ライブラリ |
| TypeScript | 5.2.2 | 型安全性 |
| Tailwind CSS | 4.1.13 | スタイリング |
| shadcn/ui | Latest | UI コンポーネント |
| TanStack Query | 5.x | GraphQL 状態管理 |
| Radix UI | 1.2.12 | UI プリミティブ |
| Lucide React | 0.544.0 | アイコンライブラリ |
| React Hook Form | 7.47.0 | フォーム管理 |
| Zod | 3.22.4 | バリデーション |
| Embla Carousel | 8.x | スライダー・カルーセル |
バックエンド技術スタック¶
| 技術 | バージョン | 用途 |
|---|---|---|
| Vendure | 3.5.2 | E-commerce エンジン |
| Node.js | 24.x | ランタイム |
| TypeScript | 5.7.2 | 型安全性 |
| GraphQL | 16.11.0 | API |
| PostgreSQL | 17.x | データベース |
| Redis | 7.x | キャッシュ・キュー |
| NestJS | 11.1.6 | フレームワーク |
| TypeORM | 0.3.28 | ORM |
※ 正確なバージョン情報は package.json および pnpm-workspace.yaml
を参照してください。
インフラ・運用技術スタック¶
| 技術 | バージョン | 用途 |
|---|---|---|
| Cloudflare Workers | Latest | フロントエンドホスティング |
| Cloudflare CDN/DNS | Latest | CDN・DNS |
| Fly.io | Latest | Vendure コンテナホスティング |
| Fly Redis | Latest | Redis(本番) |
| Fly Volumes | Latest | Redis 永続ストレージ |
| PostgreSQL | 17.x | データベース |
| Redis | 7.x | キャッシュ・キュー |
開発・テスト技術スタック¶
| 技術 | バージョン | 用途 |
|---|---|---|
| oxlint | 1.31.0 | コード品質チェック |
| oxfmt | 0.47.0 | コード・JSON・Markdown・YAML フォーマット |
| Nx | 20.2.0 | ビルドシステム |
| Vitest | 3.2.4 | ユニットテスト・E2E (Vendure) |
| Playwright | 1.49.0 | E2Eテスト (Storefront) |
| Testing Library | 16.3.0 | React テストユーティリティ |
| React Cosmos | Latest | コンポーネント開発環境 (Storefront / Dashboard) |
| GraphQL Codegen | 5.0.0 | 型安全なGraphQLクライアント |
| MSW | 2.4.9 | API モック |
デプロイメント構成¶
アプリケーション構成¶
# Fly.io アプリケーション構成
ritsubi-vendure:
- Primary Region: nrt (Tokyo)
- Instance Count: 2 (HA構成)
- Resource: 2x shared-cpu-1x (1GB RAM)
ritsubi-worker:
- Primary Region: nrt (Tokyo)
- Instance Count: 1
- Resource: 1x shared-cpu-1x (1GB RAM)
ネットワーク設計¶
内部通信(IPv6 Private Network)¶
Vendure API → PostgreSQL: 内部プライベート接続
Vendure API → Redis(Fly-hosted): 内部プライベート接続
Worker → Redis/PostgreSQL: 内部プライベート接続
外部通信(Public Internet)¶
Cloudflare Workers Edge → Vendure GraphQL: HTTPS (Port 443)
外部API → Vendure: HTTPS (Port 443, Admin GUI用)
Webhook → Vendure: HTTPS (Port 443, 決済通知用)
セキュリティ設計¶
ネットワークセキュリティ¶
- プライベートネットワーク: Fly.io IPv6 プライベートネットワーク使用
- 外部アクセス制限: Admin GUI のみ特定IPからアクセス許可
- HTTPS強制: すべての外部通信でTLS 1.3使用
- CORS設定: Storefront のみからの API アクセス許可
アプリケーションセキュリティ¶
// Vendure セキュリティ設定例
export const securityConfig = {
auth: {
sessionDuration: "7d",
sessionCacheStrategy: new RedisSessionCacheStrategy({
redisOptions: {
host: process.env.REDIS_URL,
password: process.env.REDIS_PASSWORD,
tls: { rejectUnauthorized: false },
},
}),
},
cors: {
// Storefront Worker 経由の Shop API は alias domain を直接許可対象に増やさず、
// Worker 側で canonical origin に正規化してから Vendure へ送る。
origin: ["https://order.ritsubi-platform.com", "https://order.ritsubi.co.jp"],
credentials: true,
},
adminApiPath: "admin-api",
shopApiPath: "shop-api",
};
データベース設計¶
PostgreSQL 構成(Fly Postgres)¶
# Fly Postgres 設定
Database: ritsubi-vendure-db
Instance: shared-cpu-1x (1GB RAM, 10GB Storage)
Region: nrt (Tokyo)
Backup: 自動日次バックアップ (7日間保持)
High Availability: マスター/スタンバイ構成
接続管理¶
// Vendure データベース接続設定
export const dbConfig = {
type: "postgres",
host: process.env.DATABASE_HOST,
port: 5432,
username: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
ssl: { rejectUnauthorized: false },
synchronize: false, // 本番環境では false
migrationsRun: true,
logging: ["error", "warn"],
poolSize: 10,
connectionTimeoutMillis: 5000,
idleTimeoutMillis: 10000,
};
Redis 構成(本番・ステージングとも Fly.io 専用 Redis)¶
# Redis 設定(Fly.io 専用 app)
Runtime: redis:7-alpine
Region: Tokyo (nrt)
Persistence: Fly Volume + AOF
Eviction Policy: noeviction
TLS: 無効(`*.internal` で内部接続)
用途別設定¶
// Redis 用途別接続設定
export const redisConfig = {
// セッションキャッシュ用
session: {
host: process.env.REDIS_HOST,
port: 6379,
password: process.env.REDIS_PASSWORD,
tls: { rejectUnauthorized: false },
keyPrefix: "session:",
ttl: 7 * 24 * 60 * 60, // 7日間
},
// BullMQ ジョブキュー用
queue: {
host: process.env.REDIS_HOST,
port: 6379,
password: process.env.REDIS_PASSWORD,
tls: { rejectUnauthorized: false },
maxRetriesPerRequest: null, // BullMQ必須設定
keyPrefix: "bull:",
lazyConnect: true,
},
// 一般キャッシュ用
cache: {
host: process.env.REDIS_HOST,
port: 6379,
password: process.env.REDIS_PASSWORD,
tls: { rejectUnauthorized: false },
keyPrefix: "cache:",
ttl: 60 * 60, // 1時間
},
};
スケーリング戦略¶
水平スケーリング¶
Auto Scaling 設定¶
# fly.toml - Auto scaling設定
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 1
processes = ["app"]
[[http_service.concurrency]]
type = "connections"
hard_limit = 1000
soft_limit = 800
[metrics]
port = 9091
path = "/metrics"
インスタンス拡張方針¶
# トラフィック増加時の拡張
# CPU使用率 > 70% で自動スケールアップ
# 接続数 > 800 で新インスタンス起動
# 負荷レベル別インスタンス数
Low Traffic (通常時): 2 instances
Medium Traffic (繁忙期): 4 instances
High Traffic (キャンペーン): 6 instances
垂直スケーリング¶
リソース拡張パス¶
# スケールアップパス
Tier 1 (開始): shared-cpu-1x (1GB RAM)
Tier 2 (成長): performance-1x (2GB RAM)
Tier 3 (拡大): performance-2x (4GB RAM)
Tier 4 (企業): performance-4x (8GB RAM)
パフォーマンス最適化¶
Vite / Cloudflare 最適化¶
- Vite build はハッシュ付き
/assets/*を生成し、Cloudflare Workers のASSETSbinding 経由で公開する。 worker.jsは/assets/*・/favicon.ico・/manifest.jsonなどの静的要求を fallback せず返し、それ以外の navigation request だけを SPA へフォールバックする。vite.config.tsでは不要な依存の pre-bundle を除外し、mock/test 用パッケージを production bundle へ混入させない。
Vendure 最適化¶
// Vendure パフォーマンス設定
export const vendureConfig: VendureConfig = {
// データベース最適化
dbConnectionOptions: {
type: "postgres",
poolSize: 10,
cache: {
type: "redis",
options: redisConfig.cache,
},
},
// GraphQL 最適化
apiOptions: {
shopApiPlayground: false, // 本番では無効
adminApiPlayground: false,
introspection: false,
cors: securityConfig.cors,
},
// ジョブキュー最適化
jobQueueOptions: {
jobQueueStrategy: new BullMQJobQueueStrategy({
connection: redisConfig.queue,
}),
},
// セッション最適化
authOptions: {
sessionCacheStrategy: new RedisSessionCacheStrategy({
redisOptions: redisConfig.session,
}),
},
// アセット最適化(Cloudflare R2)
assetOptions: {
storageStrategyFactory: configureS3AssetStorage({
bucket: process.env.VENDURE_R2_BUCKET_NAME,
credentials: {
accessKeyId: process.env.VENDURE_R2_ACCESS_KEY_ID,
secretAccessKey: process.env.VENDURE_R2_SECRET_ACCESS_KEY,
},
nativeS3Configuration: {
endpoint: process.env.VENDURE_R2_ENDPOINT,
region: process.env.VENDURE_R2_REGION ?? "auto",
forcePathStyle: process.env.VENDURE_R2_FORCE_PATH_STYLE === "true",
},
}),
},
};
スケジューラ設定(DefaultSchedulerPlugin)¶
apps/vendure-server/src/vendure-config.shared.tsでDefaultSchedulerPluginを有効化済み。- 開発環境では
manualTriggerCheckInterval: '5s'、本番系は'10s'を指定し、手動トリガー監視の反応速度を調整。 - 既定で
runTasksInWorkerOnlyがtrueのため、スケジュールタスクはワーカープロセス側で処理される想定。 - 新規タスクは同ファイル内の
schedulerOptions.tasksに追記し、想定SLAに合わせてtimeout等のオプションを設定する。 - マルチインスタンス運用時でも単一実行を保証するため、BullMQ 導入済みの場合も当プラグインを併用する。
災害復旧・可用性¶
バックアップ戦略¶
Database Backup:
- Vendure PostgreSQL: GitHub Actions (hourly) -> Cloudflare R2 (custom dump, 30日保持)
- WordPress MariaDB: systemd timer (hourly) -> Cloudflare R2 (gzip SQL, 30日保持)
- PITR / cross-region backup は現行実装では未提供
Operational State:
- Redis は session/shared cache の再構築前提
- Storefront maintenance KV は単一キーの手動再作成前提
Object Storage:
- R2 asset bucket は primary storage であり、独立バックアップではない
Application Backup:
- Git リポジトリ(GitHub)
- Docker イメージ(Fly Registry)
- 設定ファイル(環境変数はSecrets管理)
[!NOTE] current implementation / gap analysis の正本は
docs/05-delivery/maintenance/backup-and-restore.mdを参照してください。
障害対応¶
graph TD
A[障害検知] --> B{障害レベル判定}
B -->|Level 1| C[自動復旧]
B -->|Level 2| D[手動介入]
B -->|Level 3| E[緊急対応]
C --> F[ヘルスチェック]
D --> G[ログ調査]
E --> H[バックアップから復旧]
F --> I[正常化確認]
G --> J[原因特定]
H --> K[サービス復旧]
I --> L[監視強化]
J --> M[修正デプロイ]
K --> N[事後検証]
RTO/RPO 目標¶
Recovery Time Objective (RTO):
- Level 1 障害: 5分以内
- Level 2 障害: 30分以内
- Level 3 障害: 2時間以内
Recovery Point Objective (RPO):
- データベース: 1時間以内
- Redis キャッシュ: 即座に再構築可能
- ファイルアセット: 24時間以内(Cloudflare R2)
[!NOTE] 上記は目標値です。現在の実装到達状況は
docs/05-delivery/maintenance/backup-and-restore.mdを正本とします。
運用考慮事項¶
デプロイメント戦略¶
Deployment Strategy: Blue-Green Deployment
- 新バージョンを並行環境にデプロイ
- ヘルスチェック通過後にトラフィック切り替え
- 問題発生時は即座にロールバック
Release Schedule:
- メジャーリリース: 月1回(計画停止あり)
- マイナーリリース: 週1回(無停止デプロイ)
- ホットフィックス: 随時(緊急対応)
監視・アラート¶
Key Metrics:
- Response Time: < 200ms (P95)
- Error Rate: < 0.1%
- Availability: > 99.5%
- Database Connections: < 80%
- Redis Memory Usage: < 70%
Alert Thresholds:
- Critical: サービス停止、データ損失リスク
- Warning: パフォーマンス劣化、リソース逼迫
- Info: 通常の運用イベント
メンテナンス¶
Scheduled Maintenance:
- Database Maintenance: 月1回(日曜深夜)
- Security Updates: 月2回
- Performance Tuning: 四半期ごと
Maintenance Window:
- 時間: 日曜 2:00-4:00 JST
- 通知: 1週間前にユーザーへ告知
- ロールバック: 30分以内で実行可能
文書バージョン: 1.0 作成日: 2025年9月17日 次回レビュー: 2025年12月17日(サービス開始3ヶ月後)