404 Not Found

404 Not Found


nginx

プロジェクト設計 — PriceTracker フルスタックアーキテクチャ青写真

プロジェクト設計は建物を建てる前の青写真のようなもの - 青図面なしで建設を始めると, 3階に到達した時点で基礎が足りないことに気づき, 取り壊してやり直すしかなくなります。良い青写真は描いて終わりではなく, 建設全体を通じての指針となります。

1. 学ぶ内容


2. Aliceの実話

(1) 悩み:機能が増えるほどコードが散乱

PriceTrackerはシンプルなAPIから始まりましたが, 機能が増えるにつれ統一された設計がなく - 製品と価格の境界が曖昧, サブスクリプションロジックが10のエンドポイントに散在, 通知システムがビジネスロジックと密結合していました。新しい機能を追加するたびに5つのファイルを修正し, 回帰バグが頻発していました。

(2) ドメイン駆動設計による解決策

ドメイン駆動設計 (DDD)はビジネスの視点からコアドメインと境界を特定します:Product集約, Price集約, User集約, Subscription集約 - 各集約は明確な境界と責任を持ち, 集約間の通信はイベントを通じて行われ, 相互依存を排除します。

(3) 成果

新機能の追加時には該当する集約内のコードのみを修正すればよく, 変更はシステム全体に影響しなくなりました。サブスクリプションロジックはSubscription集約に集約され, 通知はPriceUpdatedイベントでトリガーされ, 完全に疎結合になりました。


3. 要件分析

(1) コアユースケース

ロール ユースケース 優先度
ユーザー (Alice) サインアップ/ログイン P0
ユーザー 製品価格の確認 P0
ユーザー 価格変動通知のサブスクライブ P1
管理者 価格の一括インポート P0
管理者 製品CRUDの管理 P0
フロントエンド (Bob) APIを呼び出してデータを取得 P0
DevOps (Charlie) システム健康状態のモニタリング P1
システム 自動価格スクレイピング P2

(1) ▶ サンプル:ユースケースを優先度順にソートするスクリプト

PYTHON
# scripts/prioritize_use_cases.py
use_cases = [
    {"role": "User", "action": "register_login", "priority": "P0", "effort": 2},
    {"role": "User", "action": "query_price", "priority": "P0", "effort": 1},
    {"role": "User", "action": "subscribe_alert", "priority": "P1", "effort": 3},
    {"role": "Admin", "action": "bulk_import", "priority": "P0", "effort": 5},
]

# 優先度でソート, 同じ優先度内では工数の昇順
order = {"P0": 0, "P1": 1, "P2": 2}
sorted_cases = sorted(use_cases, key=lambda x: (order[x["priority"]], x["effort"]))
for uc in sorted_cases:
    print(f"[{uc['priority']}] {uc['role']}: {uc['action']} (effort={uc['effort']}d)")

出力:

TEXT
# 実行成功

(2) 非機能要件

要件 目標 制約
QPS 100万 (クラスタ) Nginx LB + K8s弾力性
P99レイテンシ < 50 ms Redisキャッシュ + 非同期DB
可用性 99.9% マルチレプリカ + 自動復旧
データ量 100万製品 + 数千万価格 PostgreSQLパーティショニング

4. ドメインモデル設計

(1) ER図

100%
erDiagram
    User ||--o{ Subscription : has
    User ||--o{ Product : creates
    User ||--o{ Alert : sets
    Product ||--o{ Price : has
    Product ||--o{ Alert : watched_by
    Subscription ||--o{ Feature : includes
    
    User {
        int id PK
        string email UK
        string hashed_password
        string role
        datetime created_at
    }
    
    Product {
        int id PK
        string name
        string category
        float base_price
        string sku UK
        int user_id FK
        datetime created_at
    }
    
    Price {
        int id PK
        int product_id FK
        float price
        string currency
        string source
        datetime recorded_at
    }
    
    Subscription {
        int id PK
        int user_id FK
        string plan
        datetime starts_at
        datetime expires_at
    }
    
    Alert {
        int id PK
        int user_id FK
        int product_id FK
        float target_price
        string status
    }

(2) 集約ルートの特定

集約ルート 含まれるエンティティ 境界ルール
User User, Subscription, Alert ユーザーがサブスクリプションとアラートを所有
Product Product, Price 製品が価格履歴を持つ
PriceImport バッチ情報, インポートステータス 一括インポートは独立したトランザクション

(1) ▶ サンプル:ドメインイベント定義

PYTHON
# domain/events.py
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum

class EventType(Enum):
    PRICE_CHANGED = "price_changed"
    ALERT_TRIGGERED = "alert_triggered"
    USER_SUBSCRIBED = "user_subscribed"

@dataclass
class DomainEvent:
    event_type: EventType
    aggregate_id: int
    occurred_at: datetime = field(default_factory=datetime.utcnow)
    payload: dict = field(default_factory=dict)

# 使用例:価格変動イベント
price_event = DomainEvent(
    event_type=EventType.PRICE_CHANGED,
    aggregate_id=42,
    payload={"old_price": 299.9, "new_price": 249.9, "currency": "CNY"},
)

出力:

TEXT
# 実行成功

5. 技術選定の意思決定

(1) モデル比較

ニーズ 選択肢A 選択肢B 決定 理由
Webフレームワーク FastAPI Flask/Django FastAPI 非同期 + 自動ドキュメント + 型チェック
データベース PostgreSQL MySQL/MongoDB PostgreSQL JSONB + 非同期ドライバの成熟
キャッシュ Redis Memcached Redis 豊富なデータ構造 + 永続化
タスクキュー Celery+Redis RQ/Dramatiq Celery 成熟したエコシステム + 堅牢なモニタリング
リアルタイム通信 WebSocket SSE WebSocket 双方向 + 低レイテンシ
コンテナ Docker ベアメタル/VM Docker 一貫性 + オーケストレーション

(2) システムアーキテクチャ概要

100%
flowchart TD
    Client[Client / Bob Frontend] --> Nginx[Nginx Load Balancer]
    Nginx --> API1[FastAPI Pod 1]
    Nginx --> API2[FastAPI Pod 2]
    Nginx --> APIN[FastAPI Pod N]
    
    API1 --> PG_Master[(PostgreSQL Master)]
    API2 --> PG_Master
    APIN --> PG_Master
    
    PG_Master --> PG_Replica[(PostgreSQL Replica)]
    
    API1 --> Redis[(Redis Cluster)]
    API2 --> Redis
    APIN --> Redis
    
    Redis --> CW1[Celery Worker 1]
    Redis --> CW2[Celery Worker 2]
    
    CW1 --> PG_Master
    CW2 --> PG_Master
    
    API1 --> WS[WebSocket Hub]
    API2 --> WS
    
    Prometheus[Prometheus] --> API1
    Grafana[Grafana] --> Prometheus

6. APIバージョニング戦略

(1) URLバージョン vs ヘッダーバージョン

側面 URLバージョン /api/v1/ ヘッダーバージョン Accept: v=1
可視性 高い (URLが明示的) 低い (ヘッダーに隠れる)
ルーティング シンプル (プレフィックスベース) 複雑 (ミドルウェアのパースが必要)
キャッシュ 独立したURLキャッシュ Varyヘッダーが必要
Swagger 自動グループ化 手動設定が必要
推奨 ✅ PriceTrackerでの使用方法 社内API向け

(1) ▶ サンプル:URLバージョニングルーティング構造

PYTHON
from fastapi import APIRouter

# V1ルート
v1_router = APIRouter(prefix="/api/v1", tags=["v1"])
v1_products = APIRouter(prefix="/products", tags=["products"])
v1_prices = APIRouter(prefix="/prices", tags=["prices"])
v1_auth = APIRouter(prefix="/auth", tags=["auth"])

# V2ルート (将来用)
v2_router = APIRouter(prefix="/api/v2", tags=["v2"])

# ルーターをマウント
app.include_router(v1_auth)
app.include_router(v1_products, dependencies=[Depends(get_current_user)])
app.include_router(v1_prices, dependencies=[Depends(get_current_user)])
app.include_router(v1_router)

出力:

TEXT
# 実行成功

❓ よくある質問

Q DDDにおける集約ルートの役割は何ですか?
A 集約ルートはトランザクション境界を定義し, データの一貫性を保証します。Productが集約ルートであるため, PriceProductを通してのみ変更でき, 独立して追加・削除することはできず, データの一貫性が保証されます。
Q PostgreSQLで読み書き分離はどう実装しますか?
A 書き込みにはプライマリデータベース, 読み取りにはセカンダリデータベースを使用します。SQLAlchemyで2つのエンジン (write_engineとread_engine)を設定し, 読み取り操作にはread_engineでセカンダリに接続します。レプリケーション遅延を考慮する必要があります。
Q なぜMemcachedではなくRedisを選ぶのですか?
A Redisは永続化, 複数のデータ構造 (Hash/Set/ZSet), Pub/Subをサポートしていますが, Memcachedはシンプルなキー・バリューペアのみです。Redisはより幅広い機能を提供します。
Q APIバージョンはいつ更新するのですか?
A 破壊的変更 (フィールドの削除やレスポンス構造の変更など)がある場合のみバージョンを更新します。新しいフィールドやエンドポイントの追加は破壊的変更ではなく, 新しいバージョンは不要です。
Q ドメインイベントはどう実装しますか?
A シンプルな方法はCeleryタスク (publish_price_updated_event.delay(product_id, new_price)など)を使用し, より複雑な方法はメッセージキュー (RabbitMQ/Kafka)を使用します。
Q アーキテクチャ図はどの程度詳細にすべきですか?
A チームにとって「十分な」詳細さであれば問題ありません。作りすぎは時間の無駄で, 不十分だと混乱を招きます。PriceTrackerは3層アーキテクチャと集約ルートで適切なバランスを取っています。

📖 まとめ


📝 練習問題

  1. 基本問題 (難易度 ⭐):PriceTrackerのシステムアーキテクチャ図 (FastAPI, PostgreSQL, Redis, Celery, Nginxを含む)を作成し, 各コンポーネントの役割とデータフローをラベル付けしてください。ヒント:Mermaid flowchart
  2. 応用問題 (難易度 ⭐⭐):PriceTrackerドメインモデルのER図を設計し, 3つの集約ルート (User, Product, PriceImport)を特定し, 各集約に含まれるエンティティとその境界ルールを定義してください。ヒント:Mermaid erDiagram + 集約ルート表
  3. チャレンジ (難易度 ⭐⭐⭐):PriceTrackerの完全なアーキテクチャ青写真を作成 - 要件ドキュメント (コアユースケース + 非機能要件), 技術比較表, APIバージョニングルーティング構造のコード, デプロイトポロジー図 (マスター・スレーブデータベース, キャッシュ層, Celeryクラスタを含む)を含めてください。ヒント:このレッスンで扱ったすべての内容を統合。

---|

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%