【授業レポート】システム開発(2年)第36週:DBマイグレーションとDAO設計を実装する

boy in green shirt
Photo by CDC on Pexels.com

システム開発(2年)の第36週は、設計したデータベースを実際に作り、アプリケーションから操作できる形へ進める実習でした。

前週に作成したER図とSQLを出発点に、DDLの実行、DAOの設計と実装、Service層との連携、ミニ動作テストまでを一つの流れとして確認しました。

第36週の到達点

今回の実習では、データベースとアプリケーションの間にある処理を、役割ごとに分けて実装しました。

  • DDLを実行し、テーブルと制約が設計どおりに作られたか確認する
  • DAOにデータベース操作を集め、CRUDの処理を整理する
  • SQLの値をプレースホルダで渡し、安全な問い合わせの基本を押さえる
  • コミットとロールバックを含むトランザクションの境界を決める
  • DAO、Service、UIの責務を分け、テストしやすい構造を考える

実習前に押さえた用語

用語の関係を先に整理すると、実装のどの部分を確認しているのか判断しやすくなります。

今回の実習で扱った主な用語
用語 この授業での意味
DDL テーブルや制約など、データベースの構造を定義するSQLです。
マイグレーション DDLを実行して、データベースの構造を必要な状態へ作成または変更する作業です。今回はテーブル作成が中心でした。
DAO Data Access Objectの略で、SQLの実行や検索結果の取得など、データベースへのアクセスを担当する層です。
CRUD Create、Read、Update、Deleteの頭文字で、データの作成、読み取り、更新、削除を表します。
トランザクション 関連するデータベース操作を一つのまとまりとして扱い、成功時はコミットし、失敗時はロールバックする考え方です。

DDLを実行してテーブルと制約を確かめる

各班は、前週に作成したDDLを学習環境で実行し、テーブルが期待した構造になっているかを確認しました。

作成できたかどうかだけで終わらせず、制約とデータの関係まで試すことが今回の確認点です。

確認した五つの項目

  • 主キー(PK):各行を識別する値が正しく設定されているか
  • 外部キー(FK):関連するテーブル同士の参照関係が設定されているか
  • NOT NULL、UNIQUE、CHECK:空値、重複、条件外の値を制約できるか
  • サンプルINSERT:想定したデータを登録できるか
  • JOIN:複数テーブルから必要なデータを期待どおりに取得できるか

実行結果を確認する中で、外部キーの設定漏れや制約の不足が見つかりました。

設計図を眺めるだけでは気づきにくい点も、実際にデータを登録し、検索すると確認できます。

マイグレーションを発展的に学ぶ場合は、DBマイグレーションの作成、適用、ロールバックの手順も参考になります。

DAOにデータベース操作を集める

続いて、図書館システムの書籍データを扱うBooksDaoを例に、DAOのインターフェースを検討しました。

画面や業務ルールの中へSQLを散在させず、データベース操作の窓口をDAOに集めることが狙いです。

設計した基本メソッド

  • get(book_id):IDを指定して一冊の書籍を取得する
  • find_by_title(keyword):タイトルの条件に合う書籍を探す
  • create(book):書籍データを登録する
  • update(book):既存の書籍データを更新する
  • delete(book_id):IDを指定して書籍データを削除する

メソッド名だけでなく、引数、返り値、データが見つからない場合の扱いまで決めると、DAOを呼び出す側の実装が曖昧になりません。

学習用の実装例

class BooksDao:
    def __init__(self, conn):
        self.conn = conn

    def get(self, book_id):
        cur = self.conn.cursor()
        cur.execute(
            "SELECT id, title, author, is_available FROM books WHERE id = %s",
            (book_id,),
        )
        return cur.fetchone()

    def create(self, title, author):
        cur = self.conn.cursor()
        try:
            cur.execute(
                "INSERT INTO books (title, author) VALUES (%s, %s) RETURNING id",
                (title, author),
            )
            new_id = cur.fetchone()[0]
            self.conn.commit()
            return new_id
        except Exception:
            self.conn.rollback()
            raise

このコードは、授業で扱った内容を示すための簡略例です。

掲載例は%s形式のプレースホルダを使っているため、SQLiteを含む別の接続ライブラリで試す場合は、そのライブラリに合う記法へ読み替えます。

プレースホルダとトランザクションの役割

プレースホルダは、SQLの命令部分と値を分けて渡す仕組みです。

検索IDや書籍名を文字列の連結でSQLへ埋め込まず、実行メソッドの引数として渡すことが、SQLインジェクション対策の基本になります。

トランザクションでは、処理が完了したときにコミットし、途中で例外が発生したときにロールバックします。

DAOの各メソッドで完結させるのか、複数のDAO操作をまとめるService側で管理するのかを決め、境界を統一することが設計上の確認点です。

ログについては、SQLや入力値を無制限に記録するのではなく、原因調査に必要な情報をそろえながら、取り扱いに注意すべき値を残さない方針を確認しました。

DAO、Service、UIの責務を分ける

Service層は、貸出可能かの判定や延滞時の処理など、アプリケーションの業務ルールを担当します。

UIからDAOを直接呼ぶのではなく、Serviceを間に置き、各層が担当する判断を分けました。

図書館システムでの責務の分け方
層 主な役割 今回の例
DAO データベースの読み書き 書籍の取得、登録、更新、削除
Service 業務ルールと処理の順序 貸出可否の確認、延滞時の処理、検索条件の検証
ControllerまたはUI 入力の受け取りと結果の表示 検索語の受け取り、一覧やエラーの表示

この分け方により、SQLを変更する場面と業務ルールを変更する場面を区別して考えられます。

テーブルからServiceまでを通して動かす

最後に、個別に作った部品を次の順序でつなぎ、ミニ動作テストを行いました。

  1. サンプルデータをテーブルへ登録する
  2. DAOでデータを読み取る
  3. Serviceで業務ルールを適用する
  4. 返された結果を確認する

例外が発生した班は、ログを手がかりに原因を探し、その場で修正しました。

特に確認が必要だったのは、外部キーによる参照関係と、NULLを許可しない列へ渡す値です。

レビューで見えた次の改善点

  • DAOの責務を保ち、SQLを一か所に整理できていた
  • 例外時にロールバックする流れを実装できていた
  • 返り値の型や、データがない場合の返し方を明確にする余地があった
  • Serviceへ処理を集めすぎず、業務上の役割ごとに整理する必要があった

宿題と次週の実装

今回の宿題は、実装したクラスとユースケースの対応を図にして、次週の結合へ備える内容です。

  1. 各班のDAOクラス一覧とメソッド表を作成する
  2. DAOとServiceの簡易クラス図を一枚にまとめる
  3. 主要なユースケースを一つ選び、処理フロー図を作成する

次週はDAOとServiceをWebまたはCLIのUIへ接続し、ユースケース単位で動かします。

続きは、第37週のDAO、Service、UIをつなぐ実習で紹介しています。

第36週の振り返り

第36週では、ER図とDDLで表した設計を、DAOとServiceを通じて動く処理へ変えました。

データベース操作と業務ルールを分け、例外が起きた場合の戻し方まで決めることで、各コードが何を担当するのかを説明できる状態に近づきました。

この記事に関連する株式会社greedenの取り組み

DAOとServiceの責務を分ける学びは、保守しやすいWebシステム開発の土台になります。株式会社greedenは、要件定義からDBやAPIの設計、実装、テスト、保守運用まで一気通貫で支援しています。

投稿者 greeden

コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)