Cotton Bolls: データベース定義XML
« PerlUnitと動的スコープ | トップページ | Observable関数 »
2004.07.18
データベース定義XML
受託開発をしていると,Excelを使ってデータベース定義や固定長ファイルレイアウトを提供している開発がほとんどのようだ.だが,ポケットCVSリポジトリで書いたように,こういう情報こそテキストファイルにしてCVS上で管理したほうがよいと思う.
うちの開発では,たとえ外向きがExcelファイルでも内部ではXMLファイルを使って管理している.こういった情報は,プログラムのソースコード以上に重要で,スキーマ変更による影響がシステム全体に波及しかねない.また,スキーマ定義は開発中ころころ変わるのが当たり前だ.CVSでバージョン管理せずには対応できない.
例えば,社員マスタを次のように定義する(shain.xml):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tables SYSTEM "tables.dtd">
<tables>
<table name="shain" jname="社員マスタ">
<field name="shain_cd" jname="社員コード" type="char" length="6" key="yes"/>
<field name="shiten_cd" jname="支店コード" type="char" length="2"/>
<field name="name" jname="名前" type="varchar" length="20">
<desc>全角10文字</desc>
</field>
<field name="saishuu_koushin_ymd" jname="最終更新日" type="date"/>
</table>
</tables>
他にも支店マスタ(shiten.xml)など,テーブルごとにXMLファイルを書いてバージョン管理する.
これらのXMLファイルが開発のハブとして働くことになる.つまり,これらを中心にいろいろなものを自動生成するわけだ.ざっと挙げれば次のようなものがある:
- データベース定義仕様書
- create-tables.sql(テーブル作成のsqlスクリプト)
- insert, update, delete文のスケルトン作成
- DAOクラスのスケルトン作成
ここで,最後のDAOクラスにはGeneration Gapパターンを使うこともある.
さて,上で挙げた対象ごとにrubyスクリプトを作り,Makefileにターゲットを書き込む.例えば,create-tables.sqlのターゲットは次のようになる:
create-tables.sql:*.xml ruby -Ks create-tables.rb create-tables.rsql *.xml > $@
ここで,create-tables.rbがcreate-table.sqlを作成するrubyスクリプト本体で,create-tables.rsqlがeRubyファイルだ.eRubyはjspに似た形式を持つ埋め込みrubyスクリプトで,rubyのライブラリの中では一番お世話になっている(ここを参照.作者の咳さんは来週XP祭りでしゃべるらしいです).
eRubyファイル:create-tables.rsqlの中身は次のようになる:
% for table in tables
CREATE TABLE <%= table.name %> (
% for field in table.fields
<%="%-21s %-14s %s" % [field.name, field.data_type, field.not_null] %>,
% end
PRIMARY KEY (<%= table.primary_keys.join(",") %>)
);
% end
sprintf演算子(%)を使って整形しているところはわかりにくいかもしれないが,eRubyをよく知らない人にもおおよそ理解できると思う.作るのは簡単だ.
他にinsert文の例も挙げておこう.Makefileは次の通り:
%.insert: ruby -Ks xml2insert.rb xml2insert.rsql $*.xml
このターゲットルールにより,社員マスタのinsert文が簡単に作れる:
$ make shain.insert
INSERT INTO shain
(shain_cd, shiten_cd, name, saishuu_koushin_ymd) VALUES
('aaaaaa', 'bb', 'cccccccccccccccccccc', '2004-01-01');
eRubyファイル:xml2insert.rsqlは次の通り:
INSERT INTO <%= table.name %>
(<%= table.fields.collect { |f| f.name }.join(", ") %>) VALUES
(<%= table.fields.collect { |f| f.value }.join(", ") %>);
このように,eRubyのおかげでrubyスクリプト本体はモデル(jspでいうところのJavaBean)を作るだけで済む.XMLファイルを読み込んでモデルを構築するだけだ.
XMLの読み込みにはREXMLを使っているが,Unicodeで処理されるためShift JISで書かれたXMLファイルを読むには少し工夫が必要だ.Excelファイルのこともあり,やはりShift JISで扱いたい.
このため,rexmlのメソッドをuconvでフックする.僕の場合,REXMLを直接使うのではなくrexml-sjis.rbという独自ライブラリを使っている:
require 'uconv'
require 'rexml/document'
module REXML
class Attribute
alias original_value value
private :original_value
def value
Uconv.u8tosjis(original_value)
end
end
class Element
alias original_text text
private :original_text
def text
Uconv.u8tosjis(original_text)
end
end
class Text
alias original_to_s to_s
private :original_to_s
def to_s
Uconv.u8tosjis(original_to_s)
end
end
class CData
alias original_to_s to_s
private :original_to_s
def to_s
Uconv.u8tosjis(original_to_s)
end
end
end
とりあえずこれでうまくいってるようだ.あ,それからcygwinのsetup.exeからインストールしたuconvモジュールは時々コアダンプしてしまうことがある.もしコアダンプするならよしだむさんのところから最新版uconvを再インストールすることも忘れないようにしよう.
ところで,スキーマ変更で一番大きなダメージを受けるのはテストコードだと思う.残念ながら,これについては十分な対策は見つかっていない.それでも,ほんの少しだけ修正が楽になる方法を紹介しておこう.
テーブルを扱ったテストケースでは,最初にinsert文でテーブルのレコードをセットアップすると思う.このとき,なるべくinsert文一発で行うのではなく,insert文とupdate文を組み合わせて行うほうがよい.つまり,次のようにレコード生成する:
INSERT INTO shain
(shain_cd, shiten_cd, name, saishuu_koushin_ymd) VALUES
('1', 'bb', 'cccccccccccccccccccc', '2004-01-01');
INSERT INTO shain
(shain_cd, shiten_cd, name, saishuu_koushin_ymd) VALUES
('2', 'bb', 'cccccccccccccccccccc', '2004-01-01');
UPDATE shain
SET
shain_cd = '111111',
name = '社員1'
WHERE
shain_cd = '1'
UPDATE shain
SET
shain_cd = '22222',
name = '社員2'
WHERE
shain_cd = '2'
最初のinsert文は自動生成されたinsert文で,主キーのみ一意にするよう変更する.その後テストに関係のある項目だけupdate文で更新しておく.
こうすると,たとえスキーマ変更でフィールドの追加や変更,削除があってもinsert文の修正だけで済むことが多い.気休めかもしれないが,大量にテストコードがあるとき変更が楽になるので,簡単なテーブル以外はこう書くよう心がけておいたほうがいいだろう.
01:24 PM | 固定リンク
トラックバック
この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/995858
この記事へのトラックバック一覧です: データベース定義XML:
