n階層システム設計の考慮点

第7回データレイヤの設計について

今回はデータベースやその他のデータソースおよび各種サービスへの接続、データ取得を行うデータレイヤの設計について説明します。

データレイヤ

データレイヤは、データソースやサービスとのデータやり取りや接続の手順・管理・制御を行います。この階層にはデータアクセスロジックコンポーネントやサービスエージェントが含まれます。

データアクセスロジックコンポーネントは、データソースとの接続の手順、データのやり取り、接続資源の管理などを行います。この際、接続するデータソースはDBであったり、ファイルであったりします。接続先によって接続手順は異なるため、これらの煩わしさをビジネスレイヤに意識させないようにデータレイヤに隠ぺいします。サービスインターフェイスもデータアクセスロジックコンポーネントと同様ですが、接続先がCOM+サービスであったり、Webサービスであったりします。そのため、それぞれのサービスのインターフェイスにあった方法で接続する必要があります。

それでは、各コンポーネントの設計について説明します。

1. データアクセスロジックコンポーネントの設計について

n階層アプリケーションではデータストアへの接続はデータアクセスロジックコンポーネントに集約するように設計します。これにより、ビジネスレイヤではデータストアごとの固有の設定や接続、トランザクション制御(ただし、ほとんどの業務ロジックはこの方式が多いと思われますが、複数のデータストアに対する操作が発生する場合はビジネスレイヤでトランザクション制御を行う必要があります⁠⁠、データ取得等を意識することなく、設計することができます。

① データアクセスロジックコンポーネントの設計の注意点

データアクセスロジックコンポーネントは以下の点に注意して設計します。

一つのデータストアに対する操作は一つのデータアクセスロジックコンポーネントに集約する

一つのデータストアに対する操作を集約することのより、リソースの有効利用を図ることができます。

データソースへの接続はリソースと時間がかかります。そのため、できるだけ接続したコネクションをうまく共有して使用する方式を検討します。データベースであればコネクションプールをうまく活用します。ADO.NETを経由し、各種データベースの.NETネイティブプロパイダを使用している場合、ほとんどの.NETネイティブプロパイダではコネクションプールでの接続は標準で保持されている設定になっていると思います。使用するデータベースの.NETネイティブプロパイダの設定を調べてください。

また、ファイルであれば、接続状態を維持しながら排他に注意して共有します。

データアクセスロジックコンポーネント内にはデータを保持しない

データアクセスロジックコンポーネントはビジネスレイヤの変更に影響されないように設計する必要があります。また、データアクセスロジックコンポーネントは再利用されることを前提に設計する必要があります。そのため、内部でデータの保持はしないように設計します。もし、検索結果の再利用が検討される場合はデータストア側のキャッシュ機能を利用するように設計します。

データアクセスロジックコンポーネントの内部はできる限りシンプルにする

前記しましたが、データアクセスロジックコンポーネントは再利用されることを前提に設計します。そのため、内部構造をシンプルにし、できるだけ処理ロスをなくすようにします。また、データアクセスロジックコンポーネント内でのデータの入れ替えやソート、結合などはせずに、データアクセスロジックコンポーネントの呼び出し元のインターフェイスの構造・データ型にあったデータを取得・引き渡しするようにします。

データストアがデータベースである場合、SQL文などでデータ構造およびソート・結合等は指定することができます。また、データ型についてもデータベースのデータ型をそのまま使用できる場合もあります。これらを使用することによりデータの入れ替えや型変換に費やされるリソースおよび処理時間を短縮できます。

データアクセスロジックコンポーネントでは複数データストアへのデータの更新・削除・作成などの操作を行うトランザクションを開始しない

複数データストアへのデータの読み取り以外の操作を行う場合は、業務ロジックに依存する部分が多いと思われます。そのため、業務ロジックと切り離して設計するデータアクセスロジックコンポーネントではこれらの機能を盛り込まないようにします。また、システムエラーや例外処理が発生した場合など、データアクセスロジックコンポーネント内で解決できない場合も多いため、これらの機能を盛り込まないようにします。

データアクセスロジックコンポーネントでトランザクションを開始しない場合、呼び出し元がトランザクションルートとなれるためのインターフェイスを用意する

前記の複数データソースへの操作が業務ロジック上必要な場合は、トランザクションルートをビジネスレイヤにする必要があります。そのため、データアクセスロジックコンポーネントではデータストアへの接続とデータのやり取りのみとし、呼び出し元がトランザクションを開始するためのインターフェイスを公開するように設計する必要があります。

プレゼンテーションレイヤからのデータアクセスロジックコンポーネントへの呼び出しは原則避ける

プレゼンテーションレイヤからの呼び出しは原則的にないようにします。これはセキュリティホールになる可能性があります。また、ビジネスレイヤを通らないデータ操作が行われることとなるため、入力チェックなどの機能が働かず、SQLインジェクションなどの脅威に晒される可能性があります。

② データアクセスロジックコンポーネントのインターフェイス

データアクセスロジックコンポーネントのインターフェイスとしては、第5回「④ ビジネスエンティティの設計についての注意点およびノウハウについて」でも説明したように、さまざまなデータ形式が利用できます。ここではそれぞれのデータ形式の利点と欠点について説明します。

まずはビジネスエンティティと同様のものが記述できる場合です。

XML

W3C(World Wide Web Consortium)標準準拠のため、プラットフォームに依存しない。XML スキーマ定義言語(以降XSDと略します)で定義すれば厳密な型の指定ができる。また、データストアとは非接続で使用できるため、データストアとの接続時間は短い。

ただし、XML内のタグ情報などの冗長部分が多いため、メモリなどのリソースの使用量は多い。また、XML内のエレメントへの追加・更新・削除などがあった場合のトランザクション制御は難しくなる。

DataReader

データストアへの接続効率がよく、必要なデータのみを取得するため、リソースの消費が少ない。

ただし、データストアと接続時のみデータが読みとれるため、接続時間が長くなる可能性がある。その場合はデータリソースが占有されるため、読み取り時の排他設定により、ほかのアプリケーションに影響が出る場合がある。また、データ取得のみ可能で、出力はできない。

データ型の型は基本的にデータストアのデータ型に依存する。

型なしDataSet

データストアとは非接続で使用できるため、データストアとの接続時間は短く、データの入出力が可能。型なしDataSetは内部構造を自由で定義できるため、さまざまなデータの集合体としてオブジェクト型でのやり取りができる。

型なしDataSetはシリアル化に対応しているが、厳密な型指定はできない。呼び出し元がデータ型を指定している場合は、データアクセスロジックコンポーネント内で型変換が必要となる。

データ量が少ない(単一行や1カラムのみ)の場合、DataSetの定義部があるため、リソースの使用量は多くなる。

型付DataSet

データストアとは非接続で使用できるため、データストアとの接続時間は短く、データの入出力が可能。型付DataSetは基本的にデータストアで定義されたデータ構造に依存する場合が多い。

型付DataSetはシリアル化に対応しており、厳密な型指定ができる。この場合、XSDなどで型指定をする必要がある。

データ量が少ない(単一行や1カラムのみ)の場合、DataSetの定義部があるため、リソースの使用量は多くなる。

型付DataSetはXSDなどで厳密に型指定されているため、データの受け渡し側に変更が発生した場合は再作成する必要がある。

カスタムビジネスエンティティ

データストアとは非接続で使用できるため、データストアとの接続時間は短く、データの入出力が可能。データ量が少ない(単一行や1カラムのみ)場合でも必要なデータのみをやり取りできるため、リソースの使用量は少ない。

カスタムビジネスエンティティは個別に作成するソース内で厳密に型指定するため、開発工数が大きい。また、厳密に型指定されているため、データの受け渡し側がすべてのデータ項目の型を意識する必要がある。

カスタムビジネスエンティティは厳密に型指定されているため、データの受け渡し側に変更が発生した場合は再作成する必要がある。

ビジネスエンティティにはない、スカラー値について説明します。

スカラー値

データストアへの接続効率がよく、必要なデータのみをやり取りするため、リソースの消費が少ない。データはシリアル化に対応しており、入出力が可能。データ量が少ない(単一行や1カラムのみ)の場合でも必要なデータのみをやり取りできるためリソースの使用量は少ない。

データストアと接続時のみデータがやり取りできるが、ほとんどの場合、データ量が少ないため、接続時間が長くなる可能性は少ない。

私のこれらの選択基準としては、以下のようにしています。

読み込み以外の操作の有無

データの読み込み操作しかない場合、スカラー値またはDataReader、型なしDataSetを採用しています。ただし、スカラー値はデータ量が少ない場合のみ採用しています。

データの読み込み以外の操作がある場合は、型付DataSetもしくはカスタムビジネスエンティティを採用しています。

データの多様性

データにさまざまな型指定がされている場合、型付DataSet、XMLもしくはカスタムビジネスエンティティを採用しています。

データに特に型指定がない場合、型なしDataSet、XMLおよびスカラー値を採用しています。

汎用性

汎用性を求められるならば、XMLまたはカスタムビジネスエンティティを採用します。

③ トランザクション制御について

データアクセスロジックコンポーネントを設計する上で一番注意しなければならないことはトランザクション制御です。

以下にトランザクションの種類とその利点、欠点を説明します。

データベース トランザクション

これはデータベース内で閉じるストアドプロシージャ等で使用されます。Microsoft社はストアドプロシージャの利用を下記の理由から推奨しています。

  • データアクセスの高速化が期待できる
  • データベース外部からは条件・値の受け渡しのみなので、セキュリティ保護しやすい
  • ストアドプロシージャの変更に関しては戻り値を変更しない限りデータベース内変更のみですむ
  • アプリケーションとデータベース間のやり取りが少ないので、ネットワークの状態に左右されにくい
  • SQL Server 2005以降ではCLRに対応した言語でロジックが記述できる
  • このため、可読性と生産性が向上できる
  • ストアドプロシージャは高速かつ便利であるが、保守性が損なわれるので使い過ぎないようにする
  • データベースの操作は早くても、すべてのデータをアプリケーションに渡していては速度も遅くなるので、必要なデータのみを渡すようにする
  • 内部でパラメータによりSQL文を組み立てる場合、SQLインジェクションに対応できるように設計する
  • エラー発生時にはエラーコードを呼び出し元に返すように設計する

長所としてはデータベース内に閉じられるため、パフォーマンスがよくなります。短所としては単一のデータベースの操作に限られることがあげられます。

また、各データベース用のストアドプロシージャ言語を習得する必要があり、難易度が高くなります。

手動トランザクション

これは一般的に行われている手法で、データベースのConnectionオブジェクトでトランザクションを生成します。

長所としては、設計がとても簡単で初心者でも設計しやすいです。また、明示的にトランザクションを生成・管理しているので、トランザクションスコープが分かりやすく設計できます。

短所としては、生成したトランザクションがクラスやメソッドをまたがる場合、トランザクションオブジェクトを渡すように設計する必要があります。そのため、機能的に冗長性が発生することがあります。また、トランザクションが複数の分散リソースにわたっている場合では、MS DTC関連の設計、環境構築や設定がアプリケーション設計、構築が必要になります。テスト時にもMS DTCが稼働している必要があり、テスト工数が増大する可能性がります。

自動トランザクション

Microsoft Distributed Transaction Coordinator(以降MS DTCと略します)を使用してトランザクションを制御する方式です。

長所としては、複数の分散リソースにわたっている場合、複数のデータベース間にまたがった場合のトランザクションを制御できることです。これは種類の異なったデータベースについても使用可能です。また、クラスやメソッドをまたがる場合でもMS DTCがトランザクション制御をするおかげでトランザクションオブジェクトの引き渡しの必要がありません。

短所としては、トランザクションが複数の分散リソースにわたっている場合ではMS DTC関連の設計、環境構築や設定がアプリケーション設計、構築が必要になります。また、テスト時にもMS DTCが稼働している必要があり、テスト工数が増大する可能性がります。

明示的なトランザクション

この場合はSystem.TransactionsのCommittableTransactionを使用して明示的にトランザクション管理を行います。ただし、トランザクションが複数の分散リソースにわたっている場合ではMS DTCを使用してトランザクションを制御する方式です。

本方式ではトランザクションをコミットまたは中止するためには必ずCommitメソッドもしくはRollbackメソッドを呼び出す必要があります。これらのメソッドが呼び出されない場合、トランザクションに参加するリソースはロックされ続けます。

長所としては、設計がとても簡単で初心者でも設計しやすいです。また、明示的にトランザクションを生成、管理しているので、トランザクションスコープが分かりやすく設計できます。

短所としては、生成したトランザクションがクラスやメソッドをまたがる場合、トランザクションオブジェクトを渡すように設計する必要があります。そのため、機能的に冗長性が発生することがあります。

暗黙的なトランザクション

この場合はSystem.TransactionsのTransactionScopeを使用して暗黙的にトランザクション管理を行います。ただし、トランザクションが複数の分散リソースにわたっている場合ではMS DTCを使用してトランザクションを制御する方式です。

本方式ではTransactionScopeを初期化してからDisposeメソッドの呼び出しまでの間で例外が発生しなかった場合、TransactionScopeが参加しているトランザクションを続行できます。TransactionScope内で例外が発生した場合、TransactionScopeが参加しているトランザクションはロールバックされます。TransactionScope内で正常に終了した場合、Complete メソッドを呼び出し、トランザクションを終了させます。Complete メソッドを呼び出さず、アプリケーションが終了した場合は、TransactionScopeが参加しているトランザクションはロールバックされます。

長所としては、設計がとても簡単で初心者でも設計しやすいです。また、複数のデータベース間にまたがったトランザクションを制御できることです。

短所としては、トランザクションが複数の分散リソースにわたっている場合ではMS DTC関連の設計、環境構築や設定がアプリケーション設計、構築が必要になります。

また、テスト時にもMS DTCが稼働している必要があり、テスト工数が増大する可能性がります。

Microsoft社はMSDN Libraryでは暗黙的なトランザクションを使用することを推奨しています。

確かにデータアクセスロジックコンポーネントはその中で閉じることができる単一データソースへの操作のトランザクションルートとなる場合もあります。しかし、ほとんどの場合、ビジネスレイヤで生成されたトランザクション内からの呼び出しのほうが多いと思います。そのため、私はどちらにトランザクションルートがあっても同様な設計となる暗黙的なトランザクションを用いて設計することを推奨します。

以下の表に暗黙的トランザクションのTransactionScopeOptionオプションによる状態の変化を示します。

表1 TransactionScopeOptionオプションによる状態の変化
TransactionScopeOption アンビエントトランザクション スコープが参加するトランザクション
Required なし 新しいトランザクション(ルートになる)
RequiresNew なし 新しいトランザクション(ルートになる)
Suppress なし トランザクションなし
Required あり アンビエントトランザクション
RequiresNew あり 新しいトランザクション(ルートになる)
Suppress あり トランザクションなし

アンビエントトランザクション:アンビエントトランザクションとは、実際にコードが実行されるトランザクションのこと。

(出典:Miceosoft社 MSDNライブラリ)

④ 排他制御について

トランザクション制御を設計する上で排他制御についても検討する必要があります。排他制御には大きく分けて2つの方式があります

楽観排他制御(オプティミスティック排他制御)

楽観排他制御は、トランザクションの途中でデータ更新・削除時に対象データが(読み取り時点から)変更されたかどうかを比較し、更新の有無を決定する方法です。更新の有無の判定には一般的に3つの方法があります。

変更項目のつきあわせ

変更対象項目のみを変更前に読みとっておき、更新前に変更の有無を判定する方法。

全項目のつきあわせ

変更対象項目があるレコードのすべてを読みとっておき、更新前に変更の有無を判定する方法。BLOB型などの大容量データがある場合、非現実的となる。

Timestamp 値のつきあわせ

変更対象項目があるレコードの更新タイムスタンプを読みとっておき、更新前に変更の有無を判定する方法。

悲観的排他制御(ペシミスティック排他制御)

悲観的排他制御は、トランザクションの途中で更新対象が変更されないように、データ読み取り時にテーブルロックやロックフラグ、ステータステーブルなどを用いて排他制御を行います。本方式はアプリケーションのパフォーマンス、運用性、操作性に大きく関わってきますので、設計段階での十分な検討が必要です。

また、排他制御とは異なりますが、業務ロジックのルールとして「後勝ち」があります。これは、⁠後から更新・削除したものが先に更新した内容を上書きする」と言うルールで、アプリケーション内で機械的に制御せず、運用で問題を回避する方法です。

これらの排他制御・更新ルールについては業務要件に左右されるため、これといった決め手がないのが現状です。

2. サービスエージェントの設計について

ビジネスレイヤのコンポーネントが外部のサービスに接続する必要がある場合、サービスエージェントを経由して接続します。サービスエージェントでは各サービスとの接続手順、データ変換、データのやり取り等のすべてを行い、ビジネスレイヤからこれらを隔離します。

また、サービスとビジネスレイヤ間での通信が長期化する場合は、通信途中の経過をその都度記録しておく必要があります。

サービスエージェントはこれらの機能についての設計を行います。

① サービスエージェントの設計の注意点

サービスエージェントの設計時の注意点について説明します。

1サービスエージェントは1サービスへのアクセスをカプセル化する

1対1の関係にすることにより、サービス側のインターフェイスおよび仕様変更の影響を局所で納めることができます。また、サービスエージェントも小型化・単純化が図れ、パフォーマンス、保守性、拡張性、独立性を保つことができます。

サービス側のデータ変更および呼び出し方法の変更などをビジネスレイヤから分離する

サービス側のインターフェイスおよび仕様変更をサービスエージェント内で吸収することによりビジネスレイヤの独立性を高めることができます。

サービスエージェントのインターフェイスはできるだけビジネスレイヤのビジネスエンティティで定義されたものを使用する

ビジネスエンティティで定義されたものを使用することによって、ビジネスレイヤ内でのデータ型変換の手間が省け、パフォーマンスが向上します。またこの際、型指定がされたビジネスエンティティを使用することによってより効率の向上が見込めます。

サービスエージェント内でサービスからのデータの検証・データ型変換を行う

サービスからのデータの検証は必ず行うようにします。サービスとサービスエージェント間の通信の盗聴・改ざんなどにより、さまざまな脅威が侵入してくる可能性があります。そのため、サービスエージェント内では必ずデータ検証を行うようにします。

また、サービスエージェント内でデータの型変換を行うことにより、ビジネスレイヤへの影響を少なくすることができます。

サービス側でセキュリティチェックが行われていても、アプリケーション側でのセキュリティチェックをサービスエージェント内で行う

セキュリティチェックレベルは各アプリケーションで異なるため、呼び出し側のアプリケーションに適合したセキュリティチェックを行う必要があります。

ただし、サービスとのサービスエージェント間で暗号化通信を行う場合は、同じものを使用する必要があります。

サービスとの通信を特定するために固有IDもしくはGUIDを使用する

これにより、サービスとの通信の特定、非同期通信への対応、通信状態の追跡などが行えるようになります。

サービスエージェント内で完結できるトランザクションはできるだけ完結させる

アプリケーションによって、ビジネスレイヤでトランザクションを生成し、データアクセスロジックコンポーネントや複数のサービスエージェントとのデータのやり取りをしてトランザクションを完結させるものもあるでしょう。この場合、サービスエージェント内で完結できるトランザクションはできるだけ完結できるように設計します。つまり、トランザクションを階層化して設計します。ただし、トランザクションの粒度や排他制御に注意して設計する必要があります。そしてこれらのトランザクションの結果をビジネスレイヤに返し、トランザクションのコミットかロールバックを決定させます。

この場合の設計には十分な事前検証を行い、実現性とリスクを明確にし、検討する必要があります。

3. LINQ技術の登場

.NET Framework 3.5以降を採用したアプリケーションでは、LINQ技術の登場により、ビジネスレイヤからデータストアに直接接続することも増えていくと思われます。

確かにLINQは非常に素晴らしい技術であり、業務ロジックから直接データストアに対していつも使い慣れた言語で操作を行える利点があります。LINQを使用したデータストアの操作には別個のクエリ言語を使用する必要がないため、クエリを簡素化することができます。つまり、それぞれのデータソースに合わせたクエリ言語を習得する必要がないということです。これは設計時の容易さもさることながら技術者の確保および教育にも大きく関わってきます。

また、Visual Studio 2008 を開発環境として使用する場合、LINQにより、コンパイル時チェック、静的な型指定、IntelliSense等のユーザ補助機能の利用が可能になるというメリットもあります。これもコーディング時の効率向上、バグ埋め込みの削減などを図ることができます。

これから、LINQに対応したデータソースが増えていくことと思われます。是非注目してください。

おすすめ記事

記事・ニュース一覧