About システムアーキテクチャ

Architecture & Patterns

システム構成と実装パターン

このアプリケーション(Engineer Rashinban)の技術スタックと、内部で採用している設計パターンについて解説します。

全体構成

System

学習メディア(静的サイト)と学習トラッカー(本アプリ)は別リポジトリ・別デプロイ経路(別のGitHub Actions)だが、 同じVPS上でnginxがドメインごとに振り分けている。

graph LR
    Browser[ブラウザ]

    subgraph VPS["ConoHa VPS(同一サーバー)"]
        Nginx[nginx]
        subgraph Site["engineer-rashinban-site(Astro・静的サイト)"]
            SiteDist[dist]
        end
        subgraph App["engineer-rashinban-app(Spring Boot)"]
            Systemd["systemd: rashinban-app"]
            PG[(PostgreSQL)]
        end
    end

    Browser -->|nanade.net| Nginx
    Browser -->|app.nanade.net| Nginx
    Nginx -->|静的配信| SiteDist
    Nginx -->|"reverse proxy :8080"| Systemd
    Systemd --> PG
			

技術スタック

  • Java 21 / Spring Boot 3.5
  • Thymeleaf + Tailwind CSS v4(ダーク / ライト)
  • Spring Data JPA + PostgreSQL(本番)/ H2(開発)
  • Spring Security(簡易ログイン・CSRF保護)
  • springdoc-openapi(Swagger UI)
  • GitHub Actions(CI/CD・自動デプロイ・サイト同期)

パッケージ構成

com.nanade.rashinban
├── domain          … モデル・遷移・推薦 Policy
├── infrastructure  … JPA Repository
├── web             … Controller / Service / DTO
└── config          … シード・設定

使っているデザインパターン

実装コードを見る →
パターン 場所 役割
Repository SkillRepository 永続化の抽象
Strategy NextSkillPolicy 「次の一手」推薦の差し替え
State ProgressStatus 進捗遷移の可否
Builder Skill.Builder シードデータの組み立て
Observer SkillStatusChangeNotifier / StatusChangeLogRecorder ステータス変更を購読者へ通知

現在の規模

  • スキル件数: 46 件(サイト記事と連動、手書きシードではない)
  • 自動テスト: 56件
  • REST APIエンドポイント: 10本
  • 使用デザインパターン: Repository / Strategy / State / Builder / Observer

データの流れ:サイト記事 → スキルツリー

Data Flow

17件の手書きシードから、サイト記事と連動する動的カタログに置き換えた(下記Phase 1)。

graph TB
    Site["サイト記事frontmatter + クイズ"]
    Sync["tools/site-sync export.ts"]
    JSON["catalog JSON(app repoにコミット)"]
    Sync2["起動時: CatalogSynchronizer"]
    DB[(PostgreSQL: skills)]
    Web["REST API / Thymeleaf画面"]

    Site -->|読み取り専用checkout| Sync
    Sync -->|生成・コミット| JSON
    JSON -->|classpathから読込| Sync2
    Sync2 -->|code単位の冪等upsert| DB
    DB --> Web
			

engineer-rashinban-siteは参照のみ・変更しない。GitHub Actions(下記Phase 5)が週次でこの同期を自動実行し、差分があればPRを作る(mainへの直接pushはしない)。

メソッド パス 説明
GET/api/skills一覧・絞り込み
GET/api/skills/{code}詳細(ロック状態・未完了前提込み)
PUT/api/skills/{code}/status進捗更新(State遷移・ロック検証あり)
PUT/api/skills/{code}/favoriteお気に入り切替
PUT/api/skills/{code}/tagsタグ全置換
GET/api/skills/{code}/quizクイズ出題(正答は含めない)
POST/api/skills/{code}/quiz/submission採点(サーバーサイド)
GET/api/me/quiz-attempts自分の受験履歴(要ログイン)
GET/api/dashboardダッシュボード集計
GET/api/metaカテゴリ・ステータス等の選択肢

ここまでの進化

Phase 1〜5

「17件固定のシード」から始まったこのアプリは、5段階に分けて実際のサイトコンテンツと連動する仕組みへ進化した。

  1. Phase 1: スキルツリーの動的化 ― 手書きの17スキルをやめ、学習メディア側の記事frontmatterとクイズデータから、カテゴリ・前提スキルの依存関係を自動導出する仕組みに置き換えた。 いま画面に出ている46件のスキルは、すべて実際の記事に対応している。
  2. Phase 2: 読み取りREST + OpenAPI ― 画面用のThymeleafコントローラーとは別に、同じサービス層を呼ぶだけのJSON APIを追加。Swagger UIで全エンドポイントを確認できるようにした。
  3. Phase 3: クイズ連携 ― 学習メディア側の理解度テストをアプリ内で受験できるようにした。採点は必ずサーバー側で行い、正答はクライアントへ事前送信しない。 合格するとその場でスキルを「完了」にできる。
  4. Phase 4: 書き込みRESTの完成 ― お気に入り・タグ編集もスキルコード単位のREST APIへ統一し、初期実装(ID単位)を置き換えた。
  5. Phase 5: サイト同期の自動化 ― 学習メディア側の記事更新を週次で検知し、差分があれば人間のレビュー用にPRを自動作成するGitHub Actionsを追加。 mainへの直接pushは行わず、必ず人がレビューしてからマージする設計にした。