ビジネスルールとは
業務を進める上で、さまざまな決めごと、約束事があります。
・販売価格
・15:00までの注文は、翌日配送
・7日前までのキャンセルは、キャンセル料を請求しない
・小口の注文は宅配便、大口の注文は、倉庫から直送
・先着100名は、10%の特別割引をする
・和書と雑誌は、ポイントで割引はできない
・洋書はポイントで割引をする
・送料は商品種類にかかわわらずポイントで割引できる
業務アプリケーションでは、このビジネスルールを明確にして、実装することが、重要課題です。ビジネスルールがうまく実装されたシステムは、使いやすく、ビジネスの強力な道具になります。
ビジネスは生き物ですから、ビジネスルールは状況によって、変化します。この変化に短期間で低コストに対応できるシステムほど、ビジネスにとって、価値の高いシステムになります。
今回は、このビジネスルールの実装をデータベース中心で行う方法をとりあげます。
ビジネスアプリケーションの設計の中核です。もっとも楽しい部分です。
ビジネスルールの実装パターン
業務アプリケーションで、これらのルールを実装する方法は、次の三つがあります。
・プログラムのコードで記述( if 文など )
・ルールをDBのテーブルに記述(価格、割引率など)
・テーブルに制約を宣言する ( NOT NULL, 参照制約 )
アンチパターン
ビジネスルールを、力づくで、その場しのぎで if 文を プログラムコードに追加していく。典型的なアンチパターンですね。バグの巣になりやすく、保守性がどんどん悪くなる。
DDD 、リファクタリング、Spring
ドメインオブジェクトをきちんと設計し、個々のビジネスルールを単純に実装した評価メソッド群を作る。ドメイン駆動設計 ( DDD : Domain-Driven Design )の基本思想ですね。また、Fowler のリファクタリングでは、多くのパターンが、ビジネスルールを分かりやすく記述することに関係しています。例えば、第9章「条件記述の単純化」の例が参考になります。
ビジネスルールをプログラムコードに記述する場合、定数などは、Spring の Bean ファクトリの仕組みを使って、XML ファイルに記述すると変更が容易になります。
ルールテーブル
価格や割引率は、テーブルとして実装することが普通に行われます。少し複雑なビジネスルールをテーブルとして設計・実装する例は、後述します。
ビジネスルールをテーブルとして実装すると、変更や内容確認が容易になります。
データベース制約
データベースの制約は、必須ルールや不変ルールの実装方式として最も厳格で安全な手段です。
【一意制約】
主キーをシステムで生成したサロゲート(代理)キーにした場合、商品番号とか顧客番号という自然キーを一意制約を宣言することがあります。
この情報は重複すべきではない、というルールがあれば、積極的に一意制約を宣言して、データベースに問題データを追加できないようにします。
【NOT NULL制約】
ビジネス上 必須の情報は、データベースで NOT NULL 制約を宣言します。
画面やドメインオブジェクトで必須チェックをしていても、データベースでも NOT NULL 制約を宣言すべきです。それぞれの役割と効果が異なるので、3重であっても、それぞれの必須チェックはすべきです。
画面での必須チェックは、ユーザに誤った入力をフィードバックして、その場で修正できる使いやすさの工夫です。
ドメインオブジェクトの必須チェックは、ドメイン(問題領域)の分析結果から、導かれます。ドメインのロジックとして、当然必須チェックをします。
そのオブジェクトをデータベースに永続化する時「データベース側」でも NOT NULL 制約で、整合性のチェックをすべきです。
ドメインオブジェクトには、バグがあるかもしれない。後で保守したときに、バグが混入するかもしれない。画面処理とバッチ処理で、別々のプログラムが、同じテーブルに書き込みをするかもしれない。データの整合性の最後の砦がデータベース側の制約宣言です。
テーブルは役割ごとに分割します。この設計方針だと、ほとんどのカラムは必須つまり NOT NULL 制約の対象です。
NOT NULL 制約が宣言できないカラムを発見したら、そのテーブルは、おそらく、異なる目的が混在しています。目的が単純で明確なテーブルに分割することを検討してください。
【参照制約】
参照制約は、情報の結びつきを宣言してデータの不整合を防止する仕組みです。
受注明細は、受注番号をキーとして、受注ヘッダと結びついています。
出荷レコードも受注番号をキーとして、受注と結びつきます。
テーブルには結びつきを示唆する参照カラムがあるのに、参照整合性制約(外部キー制約)を宣言していないケースが多い。
他のテーブルを参照するカラムは、かならず外部キー制約を宣言します。データの整合性を確保する簡単で確実な手段です。
ビジネスイベント系テーブルで説明したように、先行イベントと後続イベントの関係は参照性制約を一方向に限定して宣言することで、ビジネスの論理モデルをテーブルに実装します。
外部キー制約を宣言すると、INSERT や UPDATE がうまくいかないことがあります。それが正しい姿です。操作がまちがっているか、テーブルの設計がまちがっているかのどちらかです。
ひとつのテーブルで5つ以上も外部キーカラムがある場合は、おそらくテーブル設計の間違いです。役割を単純に、用途を単純に設計すれば、テーブル間の参照関係は、もっとシンプルになります。
ビジネスルールのテーブル設計:価格
ビジネスルールテーブルの典型は、価格表です。何をいくらで売るかは、ビジネスルールそのものですね。
価格は、キャンペーン期間、数量割引、お買い得セット、予約割引などで、同じ商品でも別価格になるのはよくあるケースです。
○○価格が複数あったら、それぞれの価格を別テーブルで設計・実装することを考えます。
標準価格表、キャンペーン価格表、数量割引ルール表、セット価格表、予約割引ルール表など。
KEY を検索すると、VALUE が見つかる LOOKUP テーブルパターンです。
それぞれのテーブルごとに関心を分離して、プログラムもシンプルな構造になります。また、将来、ルールの廃止・追加・変更があった場合にも、副作用を心配せずにテーブルやプログラムの変更ができます。
一つの価格表に多くの属性を持たせると、問題を複雑にして、バグのリスクが増え、保守や拡張がむずかしいシステムになります。
ひとつのテーブルにすべての価格情報をカプセル化したがるのは、典型的なアンチパターンです。入れ物は目的をシンプルにして、小さな入れ物をたくさんつくるべきです。
多くの価格情報をひとつのグループとして管理する方法がスキーマです。関連するテーブルをカプセル化するための仕組みがスキーマです。
ビジネスルールのテーブル設計:送料テーブル
送料は、重量区分と地域区分の組み合わせで決まるというケースがあります。
縦軸に重量区分、横軸に地域区分を並べた、紙ベースの送料早見表がありますね。
こういう表形式のビジネスルールのテーブル設計は、交差表パターンが定石です。
交差表パターンは、三つのテーブルで構成します。
A. 重量区分の一覧
B. 地域区分の一覧
X. 交差表(送料テーブル)
交差表は、重量区分と1対多、地域区分とも1対多の関係で関連づきます。
交差表パターンは、縦横の表形式で記述できるルールをテーブルで表現する応用範囲の広いパターンです。
デシジョンテーブルと呼ばれる、表で縦横の組み合わせごとに○×で表現するルールも、プログラムのif 文でがんばるのではなく、交差表パターンのSQL問合せで実装したほうが、シンプルだし、ルールの変更も容易になります。
状態遷移図であらわせるルールも、2次元の表形式ですから、交差表パターンがテーブル設計の候補です。
ビジネスルールのテーブル設計:デシジョンツリー
デシジョンツリー型のビジネスルールがあります。
質問にYES/NOで答えていくと、最後には、あなたは○○タイプです、というような診断ゲームにでてくるパターンですね。
テーブル設計としては、自己参照テーブルで、実装は可能です。
もし、このタイプのルールが多く、また、そのルールの変更が重要な業務であれば、ビジネスルールエンジンのパッケージやライブラリの利用も選択肢です。
プログラムコードかテーブルか
最初に書いたように、ビジネスルールは、プログラムコードでも実装できるし、テーブル+SQL問合せでも実現できます。
私は、if 文がずらった並んだプログラムは、分かりにくく、保守しにくいと思っています。多くの場合、ビジネスルールは、テーブル+SQL問合せで実装したほうが、シンプルで拡張や変更に強いシステムになります。
もちろん、ビジネスルール記述のテーブルは役割ごとに分割する設計にします。
Domain-Driven Design (DDD) の Responsibility Layers
ビジネスルールテーブルの多くは、Responsibility Layers パターンの Policy Layer のオブジェクトです。業務遂行上の判断や決定をするために必要な情報を提供します。
Policy Layer のテーブルは、下位の Capabilities Layer であるビジネスリソース系のテーブルを参照します。価格表であれば、商品テーブルへの外部キー参照をします。
商品テーブルからは、上位の Policy Layer の 価格テーブルは見えない存在です。
Domain-Driven Design (DDD) の Knowledge Level
送料テーブルで参照する、重量区分テーブル、地域区分テーブルそのものはビジネスルールではありません。ビジネスルールを構成する知識をDBのテーブルで表現したものです。
ビジネスアプリケーションでは、区分テーブルとか分類テーブルがたくさんでてきます。業務の知識や業務上の判断材料として、半ば無意識に利用されている知識を、テーブルで実装したものです。
これらの区分テーブルは、顧客や商品というビジネスリソース ( Capabilities Layer )のテーブルから外部キー参照されます。また、ビジネスルール( Policy Layer ) のテーブルからも外部キー参照されます。
つまり Responsibility Layers パターン では、うまく整理できない種類の情報が、この区分や分類のテーブルです。
これを整理する DDD パターンが、Knowledge Level です。Layer ではないので、依存関係は一方向に限定されません。
Eric Evans が書いているように、Knowledge Level は Responsibility Layers と組み合わせて使って、ビジネスルールやそれに関する知識を、分離して整理するための有用なパターンです。