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