PROJECT
HRS - ホテル予約管理システム
UMLによるオブジェクト指向分析・設計に基づく、ホテル予約からチェックアウトまでを完結できるフルスタックWebアプリケーション。チーム開発でDBスキーマ・API・LLM function callingによる予約支援チャットボットを担当。
概要
- 担当
- DB / API / LLMチャット
- 成果
- 予約からチェックアウトまでの業務フローを実装
- 状態
- 公開中
使用技術
プロジェクト概要
大学の講義で開発した、ホテル予約管理システムです。
ユースケース記述、クラス図、アクティビティ図などでオブジェクト指向分析・設計を行い、その設計をもとに TypeScript / Next.js でフルスタック実装しました。
利用者は会員登録なしで、予約番号と代表者氏名だけを使って、予約・照会・キャンセル・チェックイン・チェックアウトまで完結できます。
私は主に、Prisma/DBスキーマ設計、API・業務ロジックの実装、LLM function callingによる予約支援チャットボットを担当しました。
- 使用技術: TypeScript, Next.js, Prisma, PostgreSQL, Resend, Vitest
- デプロイ: Vercel + Neon
技術的な工夫とアーキテクチャ
1. UML分析からDBスキーマへの変換
クラス図をそのままテーブル化するのではなく、ユースケース実行後も保持し続ける必要がある情報だけを永続化する方針でテーブルを設計しました。
たとえば予約時点では部屋タイプのみを確定させ、具体的な部屋番号はreservationsとは別のstaysテーブルにチェックイン時点で割り当てる形にすることで、「予約」と「実際の宿泊実績」というライフサイクルの異なる概念を分離しています。
UML上の Boundary / Control クラスはテーブル化せず、Entity クラスと属性・関連だけをテーブル、カラム、外部キーに落とし込むルールを文書化しました。これにより、設計判断の一貫性を保ちました。

2. 一意制約を活用した楽観的な競合制御
予約作成・チェックイン・チェックアウト・キャンセルはいずれも複数テーブルを一体で更新するため、prisma.$transactionでトランザクション境界を管理しています。同時アクセスによる競合は、アプリケーション側でロックするのではなく、DBの一意制約を検知して対処する設計にしました。
- 予約番号(
HRS-YYYYMMDD-連番)が同時採番で衝突した場合、一意制約違反(PrismaのP2002)を捕捉して番号を再発行し、最大5回まで再試行 - チェックインの二重実行は
stays.reservation_idへの一意制約、チェックアウトの二重実行はlodging_charges.stay_idへの一意制約で検知 - キャンセルとチェックインが競合した場合は、
WHERE status = 'RESERVED'という条件付きupdateManyを使い、更新件数が0件なら「別の処理が先行した」と判定
これにより、悲観的ロックを使わず、通常時のスループットを落とさずに競合を検出できます。
3. LLM function callingによる「読み取り専用」予約支援チャットボット
予約状況、料金、キャンセルポリシーを自然文で問い合わせられるチャット機能を function calling 方式で実装しました。単純にLLMへ任せるのではなく、次の方針で責務を分けました。
- 決定的パーサーを前段に配置: 「明日」「来月」のような相対日付表現や、漢数字を含む人数表現は正規表現で先に解釈し、パターンに合えばLLMを呼ばずに関数を確定
- プロバイダの抽象化: Gemini / Groq / Ollama / モックを環境変数で切り替えられるようにし、LLM呼び出しが失敗してもチャット全体が落ちないようにフォールバックを用意
- 権限を絞る安全設計: チャットボットは空室検索・料金案内・手順案内だけを行い、予約の作成・変更・キャンセルの確定は実行しない設計にした。予約番号らしき文字列、メールアドレス、電話番号を検知した場合は回答を拒否し、専用画面へ誘導する

学び
UMLによる分析・設計を経由してから実装することで、「なぜこのテーブル構造・API設計になったのか」を後から追跡できるドキュメントが残り、レビューや Issue ベースの分業が進めやすくなりました。
LLM連携では、「AIに全部任せる」のではなく、確実に処理できる範囲は決定的なロジックで先に処理し、AIは曖昧な入力の解釈に使うという役割分担が、コストと信頼性の両面で有効だと学びました。