 | レベル: 中級 Ted Neward (ted@tedneward.com), Principal, Neward & Associates
2007年 12月 11日 db4o のようなオブジェクト指向データベースにオブジェクトを直接保存することで、Java™ 開発者はさまざまなメリットを得ることができます。しかし分散環境でトランザクションをサポートしたりデータを使用したり (さらにそれをセキュアに保つことが) できないと、あまり OODBMS の使い道はありません。今回は Ted Neward が「多忙な Java 開発者のための db4o ガイド」シリーズの最終回として、Java によるエンタープライズ開発での 3 つの主な懸念事項 (トランザクションと分散データ管理、そして Web アプリケーションのセキュリティー) を db4o がどのように処理するかを説明します。
 |
このシリーズについて
過去 10 年ほど、情報の保存と取得はほとんど RDBMS と同義語でしたが、最近それが変わり始めています。特に Java 開発者は、いわゆるオブジェクトとリレーショナルとのミスマッチに不満を募らせており、それを解決するためのソリューションにも我慢できなくなっています。そのため、また有効な代替手段が現れてきたことにより、オブジェクトのパーシスタンスと取得に対する新たな関心が生まれつつあります。「多忙な Java 開発者のための db4o ガイド」では db4o を紹介します。db4o はオープンソースのデータベースであり、今日のオブジェクト指向の言語やシステム、考え方を活用しています。db4o のホームページを訪れ、今すぐdb4o をダウンロードしてください。この記事で説明する例を追うためには、このダウンロードが必要です。
|
|
このシリーズでは、db4o を使ったオブジェクト指向のデータ管理に欠かせない基本事項を紹介してきました。しかし今回までに紹介しなかったことの 1 つが、Web アプリケーションでの OODBMS の利用法と、その利用法が Swing アプリケーションや SWT アプリケーションでの利用法とどう異なるのか、という点についての説明です。皆さんの中には、実作業を行う Java (または .NET) 開発者にとって無視することのできない、あらゆる問題を私が無視してきた、と言う人もいるかもしれません。
私がそうした話題を避けてきた 1 つの理由は、OODBMS の最大の魅力、つまりオブジェクト指向によるデータの保存、操作、そして取得に集中したかったためです。また OODBMS ベンダーは、トランザクション管理やセキュリティーなどのコア機能を、さまざまな RDBMS と同様の方法で、同じくらいの種類のオプションを付けて実装する傾向があります。
この「多忙な Java 開発者のための db4o ガイド」シリーズの最終回である今回は、オブジェクト指向であれリレーショナルであれ、あるいはそれ以外であれ、どのようなデータ・ストレージ・システムでも想定され、要求される、3 つの機能について説明します。では早速、deb4o がアプリケーションのセキュリティーと分散管理、そしてトランザクションをどのようにサポートしているのかを学びましょう。
複数クライアントの接続
これまで私がこのシリーズのために作成してきたコードは、データベースに対して 1 つのクライアントしかないことを想定していました。つまりデータベースに対して 1 つの論理接続のみが作成され、そしてその論理接続を通してすべての対話動作が行われます。これは、Swing や SWT のアプリケーションが構成データベースやローカルのストレージ・システムにアクセスする際の想定としては極めて妥当です。しかし Web アプリケーションでは、たとえすべての保存動作が Web プレゼンテーション層の中で行われるとしても、この想定はあまり現実的ではありません。
db4o システムの中では、データベースに対して 2 つ目の論理接続を開くことは (たとえそのデータベースがローカル・ディスク上にある場合であっても) 非常に簡単です。まず ObjectServer を作成する呼び出しを追加し、その ObjectServer から ObjectContainer オブジェクトを取得すればよいだけです。ObjectServer にポート 0 をリッスンさせると、ObjectServer は「組み込み」モードで実行され、この次に示すエクスプローラー・テストを実行する際には実際の TCP/IP ポートが開かれない (あるいは攻撃を受けない) ように命令していることになります。
リスト 1. 組み込まれた接続
@Test public void letsTryMultipleEmbeddedClientConnections()
{
ObjectServer server = Db4o.openServer("persons.data", 0);
try
{
ObjectContainer client1 = server.openClient();
Employee ted1 = (Employee)
client1.get(
new Employee("Ted", "Neward", null, null, 0, null))
.next();
System.out.println("client1 found ted: " + ted1);
ObjectContainer client2 = server.openClient();
Employee ted2 = (Employee)
client2.get(
new Employee("Ted", "Neward", null, null, 0, null))
.next();
System.out.println("client2 found ted: " + ted2);
ted1.setTitle("Lord and Most High Guru");
client1.set(ted1);
System.out.println("set(ted1)");
System.out.println("client1 found ted1: " +
client1.get(
new Employee("Ted", "Neward", null, null, 0, null))
.next());
System.out.println("client2 found ted2: " +
client2.get(
new Employee("Ted", "Neward", null, null, 0, null))
.next());
client1.commit();
System.out.println("client1.commit()");
System.out.println("client1 found ted1: " +
client1.get(
new Employee("Ted", "Neward", null, null, 0, null))
.next());
System.out.println("client2 found ted2: " +
client2.get(
new Employee("Ted", "Neward", null, null, 0, null))
.next());
client2.ext().refresh(ted2, 1);
System.out.println("After client2.refresh()");
System.out.println("client2 found ted2: " +
client2.get(
new Employee("Ted", "Neward", null, null, 0, null))
.next());
client1.close();
client2.close();
}
finally
{
server.close();
}
}
|
オブジェクト・ビューの更新
このテストは抜かりなく作成されており、実行するとテストの状況を示す行がいくつか出力されるようになっていることに注意してください。何が起きているのかを追跡することは重要です。なぜなら、ご存じのとおり db4o システムは、db4o システムが動作中に開いたオブジェクトへの参照を保持するからです。私は、メモリー内のオブジェクトへの更新が、いつ、どこで 2 番目のクライアントに「渡される」のかを確認したいのです。
問題点: set(ted1) が呼び出されると、db4o システムは、システムの内部状態を変更し、その結果 ted1 オブジェクトが変更されていて更新が必要であるということを認識します。しかし db4o システムは、ObjectContainer の commit() メソッドを使って暗黙的なトランザクションがコミットされるまで、実際の更新を行いません。この時点で、データはディスクに書き込まれますが、client2 が持つ (メモリー内の) オブジェクトのビューは、ディスク上にあるものに比べて古いままです。(デモ用のエクスプローラー・テストの出力を見てください。皆さんはこのシリーズを読みながら、ブラウザーの脇のコンソール・ウィンドウでこのコードを実行していますよね。)
修正は簡単です: client2 は、(ext() によって返される) extension オブジェクトの refresh() メソッドを使うことによって、メモリー内のオブジェクト・グラフのビューを更新します。ここで、アクティベーションの深さ (activation depth) の問題が 1 つの検討要素になることに注意してください。つまりオブジェクトを更新する際に、db4o はオブジェクト・グラフのどこまで深く入り込む必要があるのでしょう。この場合は、変更された Employee を取得するためには 1 つ下のステップまで下がれば十分ですが、当然ながらこの判断はケース・バイ・ケースでその都度行う必要があります。
client2 が持つオブジェクトのビューが更新されると、client2 はその変更を認識します。簡単なクエリーを実行してみると、その会社の (明らかに少しうぬぼれた) リーダーの新しい肩書きがわかります。
複数階層での処理
ほとんどの場合、1 つのプロセス内に複数のクライアントが存在することはありませんが、複数のプロセスにまたがって複数のクライアントが存在することがあります。例えば、サーブレット・コンテナーの中の一群のクライアントが、1 つのサーバーに対して典型的なクライアント/サーバー・スタイルで通信しているかもしれません。これを db4o で実現するための方法は、リスト 1 に示した方法とほとんど同じです。唯一の小さな違いは、サーバーを開く際にゼロでないポート番号が必要なことです。ポート番号は、サーバーがリッスンする TCP/IP ポートを表します。TCP/IP ベースのすべての通信に共通のことですが、クライアントは接続する際にホスト名とポート番号を指定する必要があります。
セキュリティーを強固にする
当然のことですが、いったんポートが関係すると、セキュリティーが問題になります。サーバーに接続してクエリーを実行することを「すべての人」に対して許可するわけにはいかないからです。従来の RDBMS 実装では、リッチで強力なセキュリティー・モデルをベンダーが提供しており、データベース接続を開くときにデータベースに送信されるクレデンシャル (ユーザー名とパスワード) に基づいて、データベース・インスタンスの一部あるいはすべてに対するアクセスを許可しています。
db4o の実装も、(少なくとも実質的には) それと異なるわけではありません。しかし、許可されるセキュリティー・ポリシーを db4o データベース・インスタンスの作成者が設定するための方法は、通常の RDBMS のシナリオとは驚くほど異なっています (リスト 2)。
リスト 2. 他と適切に通信を行う
@Test public void letsTryMultipleNetworkClientConnections()
{
ObjectServer server = Db4o.openServer("persons.data", 2771);
server.grantAccess("client1", "password");
server.grantAccess("client2", "password");
// Yes, "password" is a bad password. Don't do this in production
// code. I get to do it only because I have a special Pedagogical
// Code License from Sun Microsystems. And you don't. So don't do
// this. Don't make me come over there. I'm serious.
// Fuggedaboutit. Never. Not ever. Capice?
try
{
ObjectContainer client1 =
server.openClient("localhost", 2771, "client1", "password");
ObjectContainer client2 =
server.openClient("localhost", 2771, "client1", "password");
Employee ted1 = (Employee)
client1.get(
new Employee("Ted", "Neward", null, null, 0, null))
.next();
System.out.println("client1 found ted: " + ted1);
Employee ted2 = (Employee)
client2.get(
new Employee("Ted", "Neward", null, null, 0, null))
.next();
System.out.println("client2 found ted: " + ted2);
ted1.setTitle("Lord and Most High Guru");
client1.set(ted1);
System.out.println("set(ted1)");
System.out.println("client1 found ted1: " +
client1.get(
new Employee("Ted", "Neward", null, null, 0, null))
.next());
System.out.println("client2 found ted2: " +
client2.get(
new Employee("Ted", "Neward", null, null, 0, null))
.next());
client1.commit();
System.out.println("client1.commit()");
System.out.println("client1 found ted1: " +
client1.get(
new Employee("Ted", "Neward", null, null, 0, null))
.next());
System.out.println("client2 found ted2: " +
client2.get(
new Employee("Ted", "Neward", null, null, 0, null))
.next());
client2.ext().refresh(ted2, 1);
System.out.println("After client2.refresh()");
System.out.println("client2 found ted2: " +
client2.get(
new Employee("Ted", "Neward", null, null, 0, null))
.next());
client1.close();
client2.close();
}
finally
{
server.close();
}
}
|
db4o でのアクセス・コントロールの定義は grantAccess() メソッドで構成されているにすぎず、このメソッドがデータベース・インスタンス全体へのアクセスを許可します。これは良いことでもあり悪いことでもあります。なぜなら、これによってセキュリティーの設定シナリオは単純化されますが、その一方で最小権限の原則 (Principle of Least Privilege) を実践できなくなってしまうからです。
 |
最小権限の原則 (Principle of Least Privilege)
最小権限の原則は他の多くのセキュリティーの概念と同じく、理論的には単純ですが、実際に実装しようとすると厄介な場合もあります。この原則が言っていることは、対象のユーザーまたはコード本体には、そのユーザーまたはコードに割り当てられたタスクを実行するために必要な最低限の権限のみを与えるべきであり、それ以上の権限を与えるべきではないということです (そして当然ながら、それ以下の権限でもいけません)。つまり、例えば RDBMS のシナリオでは、RDBMS にアクセスするコードは、そのコードがアクセスするテーブルに対してのみ、基本的な SELECT/INSERT/UPDATE/DELETE 権限のみを持つ必要があり、それ以上の権限は必要ありません。こうすることで、もしそのコードが SQL インジェクションによる攻撃の対象になったとしても、そのコードはデータベースへのアクセスが制限されているため、SQL インジェクションによる攻撃を実行することはできません。
|
|
他の OODBMS はもっと柔軟かもしれませんが、現在の db4o では、この問題に対してさらに粒度の細かなレベルで対応することはできません。とりあえず現状では、確実に最小限のセキュリティー・クレデンシャルしか使用されていないようにする必要があります。もしログインすることで使用できるようになるリソースを制限できない場合は、少なくともシステムにアクセスするプリンシパルの数を制限することができます。
暗号化フォーマット
分散システムでのセキュリティーの懸念事項 (最も注目すべきものとしては、エンタープライズ・コンピューティングにおける誤謬の 4 番目 (the Fourth Fallacy of Enterprise Computing、「参考文献」の『Effective Enterprise Java』を参照)) は、ネットワークのトラフィックをリッスンしているのは信頼できる個人またはコードのみ、と信ずることはできないと言っています。これはつまり、ネットワーク上を移動するデータが平文あるいはバイナリーの形式で送信されないようにする必要があるということです (たとえデータがバイナリー形式の場合であっても、そのフォーマットがよく知られたものであれば平文と同じことです)。
セキュリティーを懸念する開発者であれば、db4o ファイルの中に保存されるデータにも懸念を持つはずです。なぜなら、このファイルは「単なるファイル」であり、従って攻撃を受けやすく、ファイルが開かれて内容が読み取られるおそれがあります (この懸念はリレーショナル・データベースに関しても同じです)。
そのためのソリューションはファイルを暗号化することですが、db4o の場合は比較的簡単です。ほとんどのシナリオでは、あまり本格的ではない攻撃者に対してデータをわかりにくくする程度なら db4o のデフォルトの暗号化機構である XTEA (eXtended Tiny Encryption Algorithm) で十分です。db4o はそれ以外のシナリオのために、サードパーティーの暗号化プロバイダーを利用できるようにするためのカスタムの暗号化「フック」を提供しています。(皆さん独自の暗号化フォーマットを定義してはいけません。それをしてもよいのは、既に確立された標準に挑戦する論文を書くことができ、世界最高の暗号専門家と数学者を集めたワークショップでそれを検討してもらい、さらにセキュリティーに関する主要な会議での口頭による批評に対して有効性を主張できる場合のみです。それらをすべて行った後であれば、その独自フォーマットを使うことを検討しても良いかもしれません。結局のところ、そうした検討をすべて行って欠陥が見つからなかったとしても、それは欠陥がないことの証明にはとてもなりません。)
データを見えなくする
ワイヤー・フォーマット (伝送路上の形式) でのデータ伝送を保護することは、ファイルを暗号化するよりも明らかに面倒です。なぜなら、6.3 までの db4o は、セキュアな伝送路で通信するための機能 (例えば SecureSocket による SSL など) を持っていないからです。これはつまり、すべての機密データは暗号化した形式で送信する必要がある、ということであり、それはオブジェクト自体の内部に何らかの形式の暗号化が必要なことを意味しています。(db4o システムがこれを直接実装していたらよかったのに、と思います。この記事の執筆時点で、db4o の 6.4 リリースには、openServer 呼び出しに対して SocketFactory を渡せるようにする計画があり、これが実現すれば、無防備な Socket を使わずに SecureSocket 接続を使えるようになります。)
カスタム・マーシャラーを使って、「横断的な」形でデータ伝送全体に渡ってデータをセキュアにできることに注意してください。名前からもわかるように、カスタム・マーシャラーは、伝送路上をデータが伝送される際のデータの圧縮方法 (そして解凍方法) を制御することができます。このための方法は、Externalizable インターフェースを使ったカスタムのシリアライゼーションの場合と非常に似ています。つまり ObjectMarshaller インターフェースを実装するクラスを作成し、readFields() メソッドと writeFields() メソッドを実装したら、db4o システムに対して、対象とするクラスの ObjectClass の marshallWith() を呼び出すことで、特定のオブジェクト・クラスにはカスタム・マーシャラーを使うように指示をします。その方法の全体を以下に示します。
Db4o.configure().objectClass(Item.class).marshallWith(customMarshaller);
|
これによって伝送路全体がセキュアになるわけではありません。攻撃者は相変わらず、保護されているオブジェクトがどんな種類のものか見ることができるかもしれません。しかしこの方法によって、ネットワークのノードからノードにデータが渡される間にデータが見られてしまうことは防ぐことができます。
db4o による「組み込みか、あるいはクライアント/サーバーか」という選択肢では要求に十分対応できないシナリオでは (例えば特定のファイル・フォーマットで保存する場合や、従来とは異なるデータ・ストレージ・リソースにデータを保存する場合など)、db4o のライブラリーを利用することで IoAdapter のサブクラスを作成することができます (このサブクラスはオブジェクトを保存する際に db4o がデータを書き込むための重要な抽象クラスです)。これによって、大部分の RDBMS システムには見られないほど柔軟にデータを保存することができます。
何事にも終わりがあります・・・
これまで取り上げた内容以外にも、OODBMS と db4o に関して調べることや説明することは数多くあります。しかし当初予定していたことは説明したので、ここでこのシリーズを終えることにします。このシリーズでは、Java 開発者の視点から、db4o とオブジェクト指向データの管理に関して十分説明し、また OODBMS を RDBMS と異なるものにしているすべての機能を紹介できたと思います。そして db4o を使用してどのようにすれば関連を容易に追跡できるか、データベース内のファーストクラスの概念として db4o が継承をどのように捉えるか、また db4o では、そうしたオブジェクトを定義したネイティブのプログラミング言語を使って、いかにそのオブジェクトの取得を簡単に行えるかについても説明しました。
また OODBMS の欠点や、RDBMS の場合と同じ懸念事項に db4o が突き当たる場合 (クライアント/サーバー・ネットワークで「ラウンド・トリップ」を処理する際のパフォーマンスの課題など) も指摘するように心がけました。
このシリーズで取り上げた例を追い、またコードを試してきた人は、db4o に関してのみならず、任意の OODBMS システムを使い始めるために必要な、基本的なスキルを一式身につけたことになります。ここで学んだことを Cache' や Versant に応用してみると、よい練習になるでしょう。大部分の OODBMS は、同じ基本的なコーディング規約や慣用的な表現を使っており、実際のところ db4o によるネイティブ・クエリーのサポートから、すべての OODBMS の一部としてネイティブ・クエリー機能を標準化するための作業が生まれています。
皆さんの求めていたものがこのシリーズに見つかり、皆さんが自分自身のプロジェクトで OODBMS を使い始められるようになったことを祈っています。リレーショナル・スキーマやリレーショナル・スキーマに関連することを何も気にする必要がないということは、驚くほど制約から解放される感じがするはずです。そこで、OODBMS で作業する際には少しリラックスして実験し、実装し、そして楽しんでください。そして皆さんの経験したことを知らせるハガキを、忘れずに私宛に送ってください。(もちろん、E メールでも結構です。)
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | |  | Ted Neward は、Neward & Associates の代表として、Java や .NET、XML サービスなどのプラットフォームに関するコンサルティング、助言、指導、講演を行っています。彼はワシントン州シアトルの近郊に在住です。 |
記事の評価
|  |