【ITニュース解説】Schema migrations for a Uniface app on SQLite - without a migration tool
2026年10月17日に「Dev.to」が公開したITニュース「Schema migrations for a Uniface app on SQLite - without a migration tool」について初心者にもわかりやすく解説しています。
ITニュース概要
UnifaceアプリのSQLiteデータベースを、ツールなしで安全に更新する手法を紹介。既存テーブルを変更せず、新テーブルを追加する「追加のみ」ルールを採用する。更新はSQLスクリプトで管理し、事前テストを徹底。アプリ自身がデータベース構造やバージョンをチェックする仕組みも導入し、手動でも確実に運用できる。
ITニュース解説
この記事は、Unifaceという開発環境で作成された顧客管理アプリケーションのデータベースを、機能追加に伴いどのように変化させていったかを紹介している。このアプリでは、データベースのテーブル数が約35個から95個へと大幅に増加した。通常、このようなデータベースの構造変更(スキーママイグレーション)には、Flywayのような専用ツールを使うことが多い。しかし、Unifaceにはそのようなツールがなく、別の開発環境のツールを導入することも適切ではないと判断されたため、ツールを使わずに安全かつ確実にデータベースを変更する独自の手法が編み出された。これは、与えられた環境の中で最善の解決策を見つけ出す、システムエンジニアの思考プロセスを示す良い事例だ。
この手法の中心にあるのは「変更せずに、ひたすら追加していく」という原則だ。一般的にデータベースのテーブルに新しい情報を追加したい場合、既存のテーブルに新しい列を追加したり、列の名前を変更したりすることがある。しかし、この変更は既存のアプリケーションのコードに大きな影響を与える可能性があり、慎重な対応が必要になる。
そこでこのアプローチでは、既存のテーブルは一切変更せず、新しい情報を格納するために、既存のデータと同じキー(例えば「顧客ID」)を持つ「新しいテーブル」を追加する。例えば、顧客の新しい会社情報を追加する際、既存の顧客テーブルに列を追加する代わりに、「CUSTOMER_PARTY」という新しいテーブルを作成し、そこに顧客IDをキーとして会社名などの情報を格納する。
この方法の最大のメリットは、既存のアプリケーションの画面(フォーム)、データの定義(エンティティ)、そしてデータ処理のロジック(サービス)を「全く触らずに済む」ことだ。データベースの変更が純粋な「追加」であるため、すでに稼働している部分に予期せぬ影響を与えるリスクが極めて低く、変更作業が非常に安全で予測可能になる。
ただし、このアプローチにはデメリットもある。顧客の全ての詳細情報を取得するには、複数の「1対1」のリレーションを持つテーブルを結合してデータを読み込む必要があるため、データ取得のSQL文が複雑になることがある。また、「顧客」というデータが複数のテーブルに分散して存在することになる。これはアプリケーションが段階的に成長していくケースでは有効な選択肢だが、最初から設計された大規模なシステムには適さない場合もある。
もう一つの重要な点は、新しく追加されたデータベースのテーブルを、Unifaceアプリケーションの「エンティティ」として定義しないことだ。Unifaceでは通常、データベースのテーブルはアプリケーションモデル内でエンティティとして定義され、それを通じて画面表示やデータ操作が行われる。しかし、この手法では、追加されたテーブルはUnifaceのエンティティとは関連付けず、アプリケーションのデータ処理を行う「サービス」コンポーネントが、SQL文を直接使ってこれらのテーブルを読み書きする。
このアプローチのメリットは、データベースの変更がUnifaceアプリケーションのモデルに全く影響を与えないため、アプリケーションの定義を保存するリポジトリへのインポート作業や、モデルとデータベースの整合性を同期する作業が不要になる点だ。これにより、データベースの変更は「純粋なデータベースの変更」に限定され、アプリケーションの再コンパイル時にモデルの不整合による警告やエラーを心配する必要がなくなる。その代わり、これらの新しいテーブルのデータを画面に直接表示することはできず、画面で情報が必要な場合はサービス操作を呼び出す必要がある。これも安全性を優先するためのトレードオフだ。
この独自の手法は、以下の6つのステップで実行される。
ステップ1: 段階ごとにSQLスクリプトを作成する 各開発段階(ステージ)ごとに、新しいテーブルの作成、インデックス(データ検索の高速化のための仕組み)の作成、そしてデータベースのバージョン番号を記録するSQL文をまとめたファイルを用意する。このバージョン番号は、後でアプリケーションが自身のデータベースの状態をチェックする際に利用される。このスクリプトは、その段階で何が変わったかを示す公式な変更履歴となる。
ステップ2: データベースファイルをバックアップする SQLiteデータベースは一つのファイルとして管理されるため、単純にデータベースファイルをコピーして保存することでバックアップが完了する。これは、もし移行作業が失敗した場合に元の状態へ迅速に戻すための最も簡単で確実な方法となる。ファイルコピーは、データベースへの書き込みが行われていない状態で行う。
ステップ3: コピーで「ドライラン」を実行する
実際のデータベースにSQLスクリプトを適用する前に、バックアップしたデータベースファイルのコピーに対して、事前にスクリプトを実行する「ドライラン」を行う。外部のSQLiteクライアントツールを使ってスクリプトを実行し、データベースの整合性チェックや、sqlite_masterというデータベース自身の構造情報を格納する特別なテーブルの内容を確認する。これにより、スクリプトに間違いがないか、期待通りの変更が適用されるかを事前に検証し、その結果を記録しておく。
ステップ4: 実際のデータベースに1文ずつ適用する
ドライランで問題がないことを確認したら、Uniface IDEのSQL実行ダイアログを使って、実際のデータベースファイルにSQL文を一つずつ適用していく。この環境では、SQL文を実行するたびに明示的にCOMMIT命令を実行して変更を確定させる必要がある。また、Ctrl+Aのような特定のショートカットキーはダイアログを閉じる機能を持つため、使用しないよう注意する。1文ずつ実行することで、万一途中でエラーが発生しても、どの時点まで変更が適用されたかを正確に把握できるため、安全性が高まる。
ステップ5: アプリケーション自身にスキーマをチェックさせる
データベースの移行後、アプリケーション自身がデータベースの構造が期待通りになっているかを確認する機能を実行する。具体的には、設定テーブルに記録されたスキーマバージョンを読み取り、現在のアプリケーションが想定しているバージョンと一致するかを確認する。さらに、sqlite_masterテーブルと照合し、新しいステージで追加されたはずのテーブルやインデックスが実際にデータベース内に存在するかを確認する。もし不足しているものがあれば、その名前を特定して異常を知らせる。これにより、例えば古いバックアップが誤ってリストアされた場合など、予期せぬデータベースの状態変化を早期に発見できる。
ステップ6: 全てのテストを実行し、チェックサムを記録する 移行作業が完了したら、アプリケーションの全てのテストスイートを実行し、機能が正しく動作することを確認する。その後、データベースファイルの「MD5チェックサム」という値を算出し、プロジェクトの記録に残す。このチェックサムはファイルの内容が少しでも変わると変化する値なので、計画された移行以外のタイミングで値が変わった場合、データベースに何らかの意図しない変更が発生したと判断でき、異常を早期に検知するための重要な指標となる。
この記事で紹介された方法は、特定の制約下で最大限の安全と効率を追求した結果だが、もし大規模なチームで開発を行う場合や、より一般的なデータベースマイグレーションツールを利用できるのであれば、異なるアプローチも考えられる。例えば、どのSQLスクリプトがいつ適用されたかを詳細に記録する専用の「バージョン管理テーブル」を導入したり、CREATE TABLE IF NOT EXISTSのように、誤ってスクリプトを二度実行してもエラーにならない「繰り返し安全なSQL文」を使用したりすることもある。ただし、この記事の筆者は、一人での作業では「エラーが発生して大声で知らせてくれた方が良い」と判断し、あえて繰り返し安全なSQL文は使わなかった。
今回の手法から得られる重要な教訓は次の通りだ。可能な限り、既存のテーブルを変更するのではなく、新しいテーブルを追加することで、変更による影響を最小限に抑えることができる。データベースの変更は、ステージごとに一つのスクリプトにまとめ、バージョン番号で管理する。実際の適用前に、必ずバックアップを取り、コピーに対してドライランを実行して変更内容を詳細に検証する。データベースへの適用は、手作業で1文ずつ慎重に行い、明示的に変更を確定させる。アプリケーション自身に、データベースのスキーマ構造が期待通りかを確認させる仕組みを導入する。これらの教訓は、ツールが利用できない状況や、手動での変更が避けられない状況においても、計画的かつ注意深い手順を踏むことで、データベースの安全な変更と運用を実現できることを示している。これは、システムエンジニアとして、どんな状況でも問題解決に取り組む上で非常に価値のある考え方だ。