第1回:データアクセスことはじめ(前編)
- INDEX -
システムを構築する上で必須となるデータベースアクセスの機能、皆さんはどのように実装しているでしょうか?JDBCで記述/EJB Entity Bean(BMP/CMP)を利用/データアクセスフレームワークを利用、等様々な実装方法を選択されているかと思います。
この連載では、様々な観点からデータアクセスに関わる事項を取り上げ、皆ささんがデータベースアクセスについて、少し考えてみる場になればと思っています。まず今回のデータアクセスことはじめ(前編/後編)では、これから様々なデータベースアクセスに関する事項を扱っていく上でのベースとなる知識を取り上げます。
現在、Javaプログラミング言語を用いてエンタープライズシステムを開発する場合、要件変更への設計・実装の変更の容易性、JDBC、EJB Entity Beanなどのデータアクセス要素技術とのマッピングの容易性、等々の理由により、システム全体を論理的な階層に分け考えるのが一般的です。そのシステムの論理的な階層は、階層内に包含される機能別に分類されます。各論理的階層の名称/分け方(階層数)は、人により様々な名称/分け方(階層数)で呼ばれています。今回は、各階層の役割をある程度明確に出来るように、すこし細かめに分けた以下の論理的な階層で説明していきます。
上記システムの論理的な階層は、Java環境特有のものではなくMicrosoft .NET等、他のプラットフォームでも似たような階層に分類されます。もっと言えばJavaの中でも人により分類方法が異なります。今回の階層で分類した場合、其々の階層の役割と、Javaにおける実装のための技術は以下のようになります。 【表】論理的階層の役割と実装技術
図の通りこれら階層は、サービス層によりユーザインタフェース側かビジネスロジック側かに分けることが出来ます。 また、これら階層は必ず実装しなければいけないものではありません。例えば、サービス層はクライアント側からのインタフェースとしての機能を提供するものですが、ドメイン層の実装によっては、ドメイン層自身にサービス層の機能を含め、サービス層が存在しない場合もあります。 今回のコラムでは、この中からデータベースアクセスに関わりのある、ビジネスロジック側の「ドメイン層」「パーシステンス層」を中心に扱っていきます。また、「データストア」に関しては、リレーショナルデータベースやオブジェクト指向データベースやメッセージ指向ミドルウェア(MOM: Message Oriented Middleware)等が考えられますが、最も選択される比率が高いと思われるリレーショナルデータベースを中心に扱っていきます。
それでは、まず始めにドメイン層からです。純粋にデータアクセスの役割は、論理的な階層の中でパーシステンス層が担当するのですが、このドメインロジック(ビジネスロジック)が含まれるドメイン層の実装方法でかなりパーシステンス層の実装方法の考え方が変わってきます。データアクセスを理解する上である程度、頭の中の整理が出来ていたほうが良いと思いますので少し見ておきましょう。
ドメイン層とは、「対象となる業務のドメインロジックが含まれた」層ですが、このドメイン層の実装を理解する上で有用なMartin Fowler氏が執筆された「Patterns of Enterprise Application Architecture(以下PoEAA)」の中で紹介されているアーキテクチャパターンをベースにご紹介していきます。PoEAA内ではドメイン層のアーキテクチャパターンとして、Transaction Script、Domain Model、Table Moduleの3種類を紹介しています。其々を順番にみていきます。
まず1つのアーキテクチャパターンはTransaction Scriptパターンです。一言でいうと「ドメインロジックを機能別に分け1つのプロシージャとして実装」といった感じです。
基本的にユーザインタフェース/アプリケーション層からの1つのトランザクション(リクエスト)に対応した処理を1つのプロシージャ(+ サブルーチン)としてドメインロジックを記述するパターンです。Transaction Scriptパターンからは直接データベースにアクセスするか、もしくはデータベースアクセスロジックをカプセル化したラッパークラス(パーシステンス層)を利用してデータベースにアクセスします。Transaction Script間での共通の処理は、サブルーチン化しても良いですが、トランザクション自身はTransaction Scriptパターンを実装した一つのプロシージャ内で完結する様にします。
では、実際の例をいきます。今回のコラムではオンラインオークションシステムのごく一部の単純なデータベーススキーマを元に、それにアクセスするドメイン層のアーキテクチャパターンをなるべく単純なコードで示してみたいと思います。 オンラインオークションシステムのデータベーススキーマは、ITEM/BID/SALEの3種類のテーブルを利用します。それぞれのテーブルの役割は以下の様になります。 【表】オンラインオークションシステムのテーブルと役割
これら3種類のテーブルを生成するDDL(Data Definition Language)は、以下のものを利用します。 【コード】オンラインオークションシステムのスキーマ作成DDL
このデータベーススキーマを元にTransaction Scriptパターンでドメイン層を実装してみます。Transaction Scriptパターンは、基本的にトランザクション(クライアントリクエスト)ごとにメソッドとして実装するパターンでした。下記BiddingServiceクラスのコード内では、findHighestBidPrice()メソッドとmakeSale()メソッドを実装しています。findHighestBidPrice()メソッドは、オークションアイテム内で、最高の入札額をBIDテーブルから取得し返します。makeSale()メソッドは、オークション出品者が出品時に、オークション主催者が予め定義した割引タイプ(ITEMテーブルのDISCOUNT_TYPEカラム列)を選択してあると仮定して、その割引きタイプを元に算出した落札額をSALEテーブルに挿入し、落札を確定します。 【コード】Transaction Scriptコード例: BiddingService.java
今回のTransaction Scriptパターンの例であるBiddingService.javaは、SQLを直接Transaction Script内に記述し、ドメイン層とパーシステンス層が合わさった形式で実装してみました。もちろん、SQLをDAO(Data Access Object)等のデータアクセスをカプセル化したパーシステンス層に分離することも可能です。 Transaction Scriptは、基本に1つのメソッド内(共通の処理をサブルーチン化することも可能)で完結するため、多くの手続き処理型言語になれた開発者の方にその容易性は受け入れられるのではないかと思われます。 ただ、問題はドメインロジック多くなり複雑になってきた時です。Transaction Scriptで実装した場合、1メソッドに着眼して処理が実装されますので、必然的に他のトランザクションのことは考えなくなる(考えないことがメリットでもあるのですが)と思います。つまり、ドメインロジックが多くなればなるほど、各トランザクションでの共通したロジックであるサブルーチンが切り出しにくくなり、重複したロジックが増え、結果的に保守しにくいものになる可能があるかと思われます。
2つ目のドメイン層アーキテクチャパターンは、Domain Modelです。一言で言うと「振る舞いとデータを持ったドメインオブジェクトとして実装」といえます。
Domain Modelはオブジェクト指向分析にしたがって、抽出されたドメインオブジェクトを元に実装します。当然、そのオブジェクトには振る舞い(ドメインロジック)が含まれます。例えば、「入札(Bid)」オブジェクトには、入札金額を計算し確定するための振る舞い(ドメインロジック)が含まれる等々。当然其々のオブジェクトで振る舞い(ドメインロジック)を持っているので、それらオブジェクトの持つ振る舞い(ドメインロジック)が連携し、全体の振る舞い(ドメインロジック)を構成します。また、これらDomain Model内に含まれるオブジェクトは、継承やポリモーフィズムなのオブジェクト指向言語の特性を利用することもできますし、GoF(Gang of Four)のデザインパターンなども適用しTransaction Scriptパターンよりオブジェクト指向的な実装が可能です。
それでは、またオンラインオークションシステムを元に簡単なDomain Modelのサンプルコードを見ていきます。先ほどのTransaction Scriptでは、makeSale()メソッドがありました。このメソッドは、入札者が入札したBIDテーブルの中にある情報を元に、その入札最高額のものに、出品者が設定した落札個数による割引を考慮してSALEテーブルに挿入することにより落札を確定するものでした。仮にこのロジックが頻繁に変わりやすいものだとしましょう。例えば、実際に落札するときに出品者が割引き率を変更することができるようにしたい等、オークションサイトの特性を引き出すために様々な落札金額確定アルゴリズムが選択したいとします。このような時にStrategyパターンを利用することが出来ます。Strategyパターンは、GoFのデザインパターンの1つで「様々なアルゴリズムを呼び出し側のコードを変更せずに実行したい」時に利用するパターンです。では、Strategyパターンを適用したDomain Modelのサンプルを見ていきましょう。アイテムの情報をカプセル化したItemクラスです。 【コード】Domain Modelパターンのサンプルコード:Item.java
Itemクラスは、アイテムに関する属性をフィールド変数としてもち、其々のフィールド変数に対するアクセッサメソッドを持ちます。また、Bidクラス内のStrategyパターンを呼び出す、makeSale()メソッドがあるという単純なものです。それでは、実際のStarategyパターンを呼び出しているBidクラスを見てみましょう。 【コード】Domain Modelパターンのサンプルコード:Bid.java
Bidクラス内では、コンストラクタでデフォルトのStrategyである、DefinedSaleStrategyクラスのインスタンスを生成し、フィールド変数に対し設定しています。つまり、Bidクラスのインスタンスを生成後何もせずにmakeSale()メソッドを呼び出した場合、デフォルトのStrategyであるDefinedSaleStrategyの落札金額確定アルゴリズムが適用されたSaleオブジェクトが返されます。また、デフォルトのStrategyであるDefinedSaleStrategyクラス以外のStrategyを利用するために、changeStrategy()メソッドを用意しています。このchangeStrategy()メソッドで新しいStrategyを追加することにより、呼び出し側のコードmakeSale()の実装を変更しなくても、落札金額確定アルゴリズムを変更することが出来ます。では次にStrategyパターンのインタフェースであるSaleStrategyインタフェースを見てみます。 【コード】Domain Modelパターンのサンプルコード:SaleStrategy.java
このSaleStrategyインタフェースにより、呼び出し側のコードを変更しなくても落札金額確定アルゴリズムを変更できるというポリモーフィズムを実現しています。また新たな落札金額確定アルゴリズムを追加したい場合、このSaleStrategyインタフェースを実装したクラスを開発し、Bid#changeStrategy()メソッドの引数として実行することにより、簡単にドメインロジックを変更・追加することが出来ます。次はこのSaleStrategyインタフェースを実装したデフォルトの落札確定アルゴリズムDefinedSaleStrategyクラスです。 【コード】Domain Modelパターンのサンプルコード:DefinedSaleStrategy.java
DefinedSaleStrategyクラスにより実装されたデフォルトのアルゴリズムは予めオークション出品者が設定した割引きタイプであるItemのdiscountTypeフィールド(ITEMテーブルのDICOUNT_TYPEカラム)の情報に従い、条件分岐しそのアルゴリズムを適用した落札金額を算出します。ちなみに割引きタイプと割引きアルゴリズムは予めオークション主催者が決定してあるものとします。このデフォルトの割引きアルゴリズム以外に、任意の割引率を適用できるアルゴリズムを持ったクラスが次のCustomSaleStrategyクラスです。 【コード】Domain Modelパターンのサンプルコード:CustomSaleStrategy.java
CustomSaleStrategyクラスではコンストラクタの引数として与えられた割引率により落札金額確定するアルゴリズムもったStrategyパターンの実装です。このCustomSaleStrategyクラスのインスタンスを生成し、Bid#changeStragety()メソッドの引数として実行することにより、容易に新しいアルゴリズムに変換することが出来ます。 ここまでがサンプルコードです。 今回のDomain Modelのサンプルは、GoFのデザインパターンの一つであるStrategyを実装することにより、アルゴリズムの容易な変更を実装してみました。そのほかにも例えば、オークションアイテムをカテゴリ分けするためにItemクラスを抽象クラスとして、その実装クラスBookクラス、Stationery(文具)クラス等の継承を利用したり、オブジェクト指向の特徴・利点を比較的素直に実装できるのがDomain Modelの利点です。 一方、Transaction Scriptパターンに比べるとなんだか面倒だなと思われた方もいるのではないでしょうか?設計・実装する側にある程度のスキルが求められるのも事実かと思います。
3つ目のドメイン層のアーキテクチャパターンはTable Moduleです。一言でいうと「DB内のテーブル/ビューに関連したドメインロジックを1クラスとして実装する」といえます。
今回のサンプルの場合、Domain ModelパターンもBIDテーブルに対応したBidクラス、ITEMテーブルに対応したItemクラスという構成になっておりTable Moduleと似ていますが、ドメインロジックが複雑になればなるほど、その違いは明確になります。Domain Modelは、基本的な全くテーブル構造とは関係なく設計実装されます。一方、Table Moduleはデータベースのテーブル・ビューに関連したドメインロジックを1テーブルと対の1クラスとしてカプセル化したものです。ちょうど手続き処理をフォーカスしたTransaction Scriptパターンとオブジェクト指向にフォーカスしたDomain Modelパターンの中間のイメージです。
実装は、基本的にテーブルごとに一つのクラスを作る形になります。ドメインロジックはstaticメソッドとして実装して良いですし、インスタンスメソッドとして実装することも出来ます。Table Moduleはデータベースの1テーブルの操作に対応したドメインロジックが1つのクラスにまとまるので、機能別にメソッド単位で実装したTransaction Scriptよりも構造が明確になり、共通のサブルーチン等を抜き出す事が容易になるかと思います。一方、あくまでテーブルの構成に依存するので、Domain Modelの様なオブジェクト指向の特性、継承やポリモーフィズム、Strategyパターンの様なGoFのデザインパターンの様なきめ細かなオブジェクトモデルの構造をとることは難しいと思われます。
Transaction Scriptパターン、Domain Modelパターン、Table Moduleパターンの違いを簡単に確認しました。それでは、皆さんはどれを選択すべきなのでしょうか?どれが、正しいと言うものでもないと思うのですが、PoEAAの中で各ドメイン層のアーキテクチャパターンをドメインロジックの複雑さと拡張に要する努力(コスト)の面で表したグラフがあります。Martin氏自身も科学的に証明されているわけではなく、感覚的にこんな感じと言っているグラフです。
このグラフを見てどのように感じるでしょうか?ドメインロジックが簡単なシステムはTransaction ScriptパターンやTable Modelパターンで記述して、複雑なシステムはDomain Modelパターンを利用しますか?今まで見てきたように、Transaction Script、Table Model、Domainモデルは其々、何を主体として考えるかの思考の回路が結構異なります。つまり、そんなに簡単にはドメイン層のアーキテクチャパターンを変更する事は難しいかと思われます。ドメインロジックの複雑性でシステムごとにドメイン層のアーキテクチャパターンを変更するよりも、どんなに単純なシステムでも最初からDomain Modelのアーキテクチャパターンを適用すべきなのではないかと思われます。
それでは、パーシステンス層のアーキテクチャパターンもみていきましょう・・・後編へ続く
【参考文献】
「Pattern of Enterprise Application Architecture」 Martin Fowler著 ISBN 0-321-12742-0 2002/11/05 「オブジェクト指向における再利用のための デザインパターン 改訂版」 Erich Gamma/Richard Helm/Ralph Johnson/John Vlissides 著 本位田 真一/吉田 和樹 監訳 ISBN4-7973-1112-6 改訂版 1999/11/1 |