目次


洗練されたPerl: データベース・テーブルにPerlを埋め込む

データベース極楽の境地に至るべく、RDBMS設計にPerlを埋め込む

Comments

データベースと、データベースを使うアプリケーションは、今日のコンピューター・インフラにとって必須のものです。データベースは、UNIX®の /etc/passwdファイルのようなプレーン・テキストのものから、買い物のパターンを追跡したり、クレジット・カードの不正を防ぐための巨大なものまで、至る所にあります。

この記事では、汎用のRDBMS(relational database management systems)とPerlを統合する、つまりデータベース・テーブルにPerlを埋め込む、という点に的を絞って説明して行きます。そう言った途端、データベース設計の純粋主義者達が、早くも私をつつくのを感じます。つまりこれは、標準的ではなく、移植性もない手法なのです。それは分かっているのですが、純粋主義者はPerlについて、次の2つを理解するべきなのです。

  • 「TMTOWTDI」はPerlのお念仏です。つまり「There's More Than One Way To Do It」(そうするための方法は他にも1つ以上ある)のです。
  • Perlプログラマーの神髄は、生産的安直さ(あるいは安直な生産性)です。

埋め込むのは生産的に安直なことですが、例えばJava®に移行することによる結果を理解しないのであれば、データベースにPerlコードを埋め込むのは避けるべきです。この記事では、Class::DBI CPAN モジュールを使ってデータベース・テーブルを操作します(これについては、次のセクションで説明します)。この記事は初心者にも、熟練した人にも好適なものですが、データベースのプログラミングについての知識、特にMySQLを使ったClass::DBI CPAN モジュール(私が使っている環境です)に関するプログラミングについての知識が少し必要です。最低限として、MySQLの設定と使い方、MySQLデータベースにテーブルを作る方法を理解している必要があります。(そうした知識がない方は、参考文献 に挙げた、MySQLを手早く理解するためのチュートリアルを見てください。)

Class::DBIの機能

Class::DBI モジュールは、強力なPerlモジュールであり、リレーショナル・データベース・テーブル設計をコードの中にモデル化することができます。これを使うことによって、1対1、または1対多の関係を作ることができ、少し手間をかければ、N対Nの関係を作ることさえできます。(これらに関しては全て、Class::DBI ドキュメンテーションに説明されています。参考文献にある、Class::DBI ホームページへのリンクを見てください。)

設定コードが書けてさえいれば、Class::DBI モジュールを使うのは簡単です。設定コードは通常、データベース・ドライバやユーザー名、パスワード、それがどこにあるか、など、データベースを記述した数行から成っています。そこから先、つまりレコードの挿入、削除、修正はどれも、一行での操作となります。Class::DBI を使うと、レコード操作が一行にまで単純になるため、データベースにPerlコードを埋め込むことが可能となり、また快適にもなるのです。これより長くなると、面倒すぎて扱えません。

Class::DBI は他にも、もっとたくさんのことができます。ドキュメンテーションをよく読んでください。なるほど!と額をたたきすぎて、頭痛がしてくること請け合いです(まずアスピリンを飲んで、痛みを防いでください)。

では、ちょっとしたテーブルを設定してみましょう。

テーブルを設定する

詳細に入り込む前に、まずアーキテクチャーと、Perlコード用のテンプレート・テーブルなどのテーブルを見てみましょう。例えば、オブジェクト削除には「DELETE」テンプレートを使います。次のリストは、そのテーブルをMySQLで定義する方法を示しています。

リスト1. コード・テンプレート・テーブルの定義(MySQL)
DROP TABLE IF EXISTS codetemplates;
CREATE TABLE codetemplates (
  name varchar(200) NOT NULL,
  template longtext NOT NULL,
  PRIMARY KEY  (name)
) TYPE=MyISAM;
LOCK TABLES codetemplates WRITE;
INSERT INTO codetemplates VALUES ('DELETE', '$target->delete()');
INSERT INTO codetemplates VALUES ('MODIFY', '$target->VERB(DATUM)');
UNLOCK TABLES;

これはテンプレート名をプライマリー・キーとする、ごく基本的な、2つのカラムを持つテーブルです。ですからどの2つのテンプレートも、同じ名前を持つことはありません。

プログラミングは、話すこととよく似ています。つまり動詞、名詞、形容詞などが必要なのです。実際、Perlを作ったLarry Wallは言語学者であり、明らかにその経験がPerlの進化に影響を与えています。埋め込みのPerlコードに関して言うと、必ず「名詞」(影響を受けるもの)と「動詞」(行われるアクション)が必要です。「修飾語」(文章での副詞または形容詞)は、通常は、必要ありません。

「名詞」はコードのターゲットです。これを$target と呼ぶことにし、これは必須です。アクションを行おうとするコードは、$target を用意できている必要があります。

「動詞」は、コードが行うアクションです。このアクションを、コードを評価することによって実行します。もしコードがフェールしたら、そのエラーを捉えます。Perlにはそのために、eval() ファンクションが用意されています。VERB ストリングは、修飾語アクションに対するプレースホルダーです。

「修飾語」は厄介です。単純にオブジェクトを削除する場合のように修飾語が必要ない場合には、気にする必要はありません。修飾語が必要な場合には、プレースホルダー・ストリングを提供します。このストリングはDATUMですが、この使い方は埋め込まれた修飾セクションで見ることにします。

埋め込まれた削除

埋め込まれた削除は実に簡単です。埋め込まれたコマンドは単に$target->delete()であり、その他はClass::DBI モジュールが全て処理してくれます。通常はこれでよいのですが、has_many() リレーションシップを定義してある場合には、カスケード削除をトリガーしてしまうかも知れません。

カスケード削除は最初、恐ろしい概念です。簡単に言うと、削除されるオブジェクトはどのオブジェクトに従属しているのかが、(has_many()リレーションシップによって)Class::DBI モジュールには分かる、ということです。例えば、「a carhas_many() tires」(車には多くのタイヤがある)という具合です。

通常は、従属オブジェクトを削除するのは妥当なことですが、そうでない場合も多々あります。よくある例として、ある部門と、その従業員との関係があります。従業員の存在はその部署に従属しておらず、単に部署にリンクされているだけです(従って従業員は複数部署に所属することがあり得ます)。

Class::DBI の作者は、これに対する標準的な解決方法を約束しており、それによると、has_many() リレーションシップがカスケード削除をトリガーしないように、と規定できるようになっています。現在のところ、推奨の解決方法は、リスト2に示すものです。

リスト2. カスケード削除をオンザフライで使用不可にする
My::Department->add_trigger(
 before_delete => sub
 {
  $_->department(undef) foreach shift->employees;
 });

こうすると、その部署を削除する前に、その部署の従業員それぞれと、部署との関連が断たれることになります。

標準の解決方法がリリースされているかどうか、Class::DBI のサイトをよく調べてください。現状で『文書化されておらず、また信頼性のない』方法として最新なのは、has_many()設定コールに対してno_cascade_delete => 1 オプションを使うことです。これはカスタム・トリガーよりもずっと魅力的ですが、もしClass::DBI がそのサポートを止めれば、動作しなくなります。利用者の責任なのです!

さて、カスケード削除のすべてを知り、注意して行うことを約束したので、埋め込まれた$target->delete()コードを実行する実際のコードが見られるようになりました。このコードが前提としているのは、(Class::DBI モジュールで通常行うように)リスト1から「codetemplates」テーブルを設定してあり、そのテーブルを使うためのCodeTemplate.pmモジュールを書いてある、ということです。

リスト3. 埋め込まれたコードを実行する
my $codetemplate = CodeTemplate->retrieve('DELETE');
foreach my $target (Class1->retrieve(500), Class2->retrieve(600))
{
 next unless defined $target;
 if ($codetemplate->template())
 {
  my $result = eval $codetemplate->template();
  if ($@)
  {
   print "The evaluation failed\n";
  }
  elsif (defined $result)
  {
   print "This operation resulted in [$result]\n";
  }
  else
  {
   print "This operation's result was undefined\n";
  }
 }
}

Class1とClass2は架空のものです。retrieve_all() ファンクションを使って、Class1とClass2が表す各テーブルの全行を取得することもできます。

見て分かる通り、こうした単純なコードでは当然なことながら、大部分の作業はエラー処理に関するものです。この操作の結果は、自由に保存できることに注意してください。これは、削除においては大したことではありませんが、他の操作に関しては重要なことです。

埋め込まれた修飾

修飾はもっと愉快です。鋭い目を持った人は既に、リスト1の中に修飾用テンプレートを表すコード、$target->VERB(DATUM)があることに気づいているでしょう。「data」の単数形(datumはdataの単数形)以外、このテンプレートは何を表すのでしょう?

先ほど、操作に必要な情報として、名詞、動詞、そしてオプションとしての修飾語を定義したことを思い出してください。操作が「MODIFY」であれば、修飾語(modifier)は値を検索するための、単なるオプションとなります。

例を試してみましょう。オブジェクト(ターゲットとなる名詞または主語)は$targetであり、これはEmployeeのクラスです。このクラスは、データベースのどこかにある「employees」テーブルから、フィールドの「name」を持つので、nameを設定する生コードは次のようになります。

リスト4. 単にemployee name(従業員名)を設定する
my $target = Employee->retrieve(500);
$target->name("Jonesy");

当然のことながら、こんな単純なことをしてはプロとしての私のプライドが許さず、二度とプログラマーとして働けなくなります。ですから次のように、ずっと難しくしなければなりません。

リスト5. 再度、気持ちを込めて
my $target = Employee->retrieve(500);
my $data = "Jonesy";
my $verb = "name";
my $codetemplate = CodeTemplate->retrieve('MODIFY');
my $template = $codetemplate->template();
if ($template)
{
 $template =~ s/VERB/$verb/g;
 # this is how you can get the OLD value of the field
 $retriever = $template;
 $retriever =~ s/DATUM//g;
 $old_value = eval $retriever;
 $template =~ s/DATUM/\$data/g;
}
my $result = eval $template;
if ($@)
{
 print "The evaluation failed\n";
}
elsif (defined $result)
{
 print "This operation resulted in [$result]\n";
}
else
{
 print "This operation's result was undefined\n";
}

冗談はともかく、この例が複雑になっているのは理由があります。これで、修飾語と動詞を設定すれば、以前の値を保持したまま、どのフィールドも設定できるのです。

皆さんは何かおかしなことに気がつきましたか? このコードは、削除を処理するリスト3とよく似ているのです。リスト3とリスト5を組み合わせて、汎用のテンプレート・ハンドラーを作ることが簡単にできます。削除テンプレートは置換によって全く影響を受けませんが、削除ではオブジェクトを削除するだけなので、フィールドの古い値を検索しないように十分注意する必要があります。ですから古い値の検索が、「MODIFY」テンプレートに対してのみ起こるようにします。

他に追加できるテンプレートには、当然ながらADDとSEARCHがあります。これらは、どちらも同じように動作します。つまりテンプレート・ハンドラーにテンプレートと動詞、修飾語を与えると、その結果のコードを幸福そうに実行します。

他の言語との互換性、および他の手法

「なんでPerlだけなんだ」と思う人は一人ではないでしょう。私はこの記事を、PerlやJava™、あるいは他の混合言語環境用に書こうと思ったのですが、Perlは飛び抜けて、一行テンプレートに適しているのです。複数言語を使った、これよりも長いテンプレートでは、すぐにシステムが維持不能になるでしょう。ですから、できれば一つの言語に限定し、その言語も簡潔にしておいた方が得策だと思います。Perlの代わりにPythonでも(ごく短いデータベース操作ディレクティブを使うので)動作するかも知れませんが、私はPythonでのテスト実装は試していません。

これを見て、誰でも遅かれ早かれ思いつきそうな案として、「オペコードからテンプレートへの変換テーブルをコードに入れたらどうか?」という案があるでしょう。

それは可能ですが、私は推奨しません。理由は、コードをオペコードからテンプレートへ、そしてテンプレートから実際のコードへと、実質的に2度変換することになるためです。オペコード「DELETE」は単純に、使用環境と言語において削除に適切な、何らかのファンクションにマップした方がよいでしょう。データ操作単純化のための操作抽象化の一般概念は、どのように実装しても、有効なものです。

テンプレートで表現するにせよ生コードで表現するにせよ、抽象化で鍵となるのは、『名詞-動詞』あるいは『名詞-動詞-修飾語』の形式にまで操作を単純化すべきだ、ということです。この抽象化を行ってみると、自分のソフトウェアに必要なもの、欲しいものについて貴重な教訓が得られるものです。そしてソフトウェアが、ある状態から別の状態へ、どのように移るのかが理解できるようになり、また非常に重要なこととして、一つ一つの操作を定義できれば、そうした操作を追跡し、取り消すこともできるということです。

まとめ

データベースに保存されたコード・テンプレートについて、十分楽しんでもらえたものと思います。

皆さんは、Template Toolkitにも関心があるかも知れません。これを使うと、上記で説明した単純な置換よりも、テンプレートがずっと強力なものになります。ただし、コードがすべて、テンプレートの連続になることに注意してください。テンプレートはすぐに、複雑になりすぎ、更新が困難なものになります。強力さと単純さを、うまくバランスさせ、できれば両方を追求してください。それが良き設計者のトレードマークというものです。

Template Toolkitの場合、大まかに言って、(テンプレートが一行として意図されているとして)テンプレート1つ当たり、1つ以上のループ、3つ以上の補間変数(interpolated variables)を持たないようにすることが得策です。

操作の中でコード・テンプレートを使うことを考えるのであれば、例えばJava Swingツールキットで実装されている、一般的なMVC(model-view-controller)パターンを考慮すべきでしょう。モデル、ビュー、コントローラーを分離することには多くの利点があります。この記事で示したように、操作を抽象化できれば、データのビューとモデルを分離するという、次のステップに容易に進むことができます。ビューとモデルの分離ができていない場合でも、MVCパターンは多くの状況で貴重なものです。ですから、どのように動作するのか、自分にとってどんな役に立つのかを調べてみるべきでしょう。

では、皆さんもデータベース操作を楽しんでください。そして、TMTOWTDを忘れずに!


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux, Open source
ArticleID=226661
ArticleTitle=洗練されたPerl: データベース・テーブルにPerlを埋め込む
publish-date=03092005