次の方法で共有



July 2016

Volume 31 Number 7

Cutting Edge - Code First でのリフレクション、永続化、およびドメイン モデリング

Dino Esposito | July 2016

Dino EspositoCode First は、Entity Framework (EF) の機能の 1 つで、プレーンな .NET クラスを使用してデータベースのテーブルをモデル化できるようにします。正直、Code First という名前は少し誤解を生むのではないかと考えています。ですが、内部のしくみは非常に明白です。Code First によって、使用するデータベースの構造がレイアウトされ、そのデータベースに格納されたデータを操作する総合的なオブジェクト指向 API が提供されます。

Code First は、EF4.1 で導入され、EF6 にも引き続き組み込まれています。Code First は C# や Visual Basic のクラスを使ってデータベースをモデル化するアプローチの 1 つにすぎません。EF6 までは、データベースのスキーマを推測し、EDMX 拡張子を付けてそのスキーマを XML ファイルに保存して、コードで使用するアドホック クラスを作成するために、Visual Studio デザイナーを使用することもできます。Visual Studio デザイナーでは、抽象モデルを作成することもできます。その後、この抽象モデルを使って物理データベースを作成できます。

簡単に言えば、EF6 までは同じことを行うのに 2 つの方法がありました。ただし、EDMX による方法は機能しますが、他の方法よりも多く問題を含んでいました。このようなことから、次の EF7 では EDMX のサポートは継続されなくなります。

長年、Code First はドメイン駆動設計 (DDD) に関連付けられていました。このことが、Code First と EDMX は必ずしも同じことを実現するわけではないと一般的に考えられてきた一因かもしれません。今回は、アーキテクチャ寄りの観点から Code First を取り上げ、ドメイン モデルの領域と永続化モデルの領域との区別を明確にします。Code First と LINQ を組み合わせることで、多く開発者が抱いていた昔からの夢が実現されます。 つまり、オブジェクト指向風の見かけにデータ アクセスの複雑さ (テーブル、インデックス、制約) が隠され、かつてないオブジェクト指向のデータ定義言語になります。

歴史的背景

リレーショナル データベースの操作は、SQL 言語のルールに従います。アプリケーションのコーディングは、選択したプログラミング言語のルールに従います。そのため、上位のプログラミング言語のオブジェクト指向の性質 (または手続き型の性質) と、SQL 言語とを橋渡しする抽象化層が必要になります。Microsoft .NET Framework の場合、この抽象化層にあたるのが ADO.NET フレームワークです。

ADO.NET は比較的薄い抽象化層で、.NET コードには SQL コマンドを配置するオブジェクトを提供するだけです。ADO.NET は、データベースとやり取りするデータをオブジェクト指向のアドホックなデータ構造にマッピングすることはありません。ADO.NET のデータはフラットで、データにアクセスするツールは .NET Framework と完全に統合されています。

約 10 年前、オブジェクト リレーショナル マッピング (O/RM) フレームワークが登場しました。O/RM フレームワークは、クラスのプロパティをテーブルの列にマッピングします。そのため、Data Mapper、Unit of Work、クエリ オブジェクトなど、数多くの設計パターンを実装します。O/RM フレームワークは、一連のマッピング ルールとターゲット データベースのスキーマに関する情報も内部で管理しています。管理するのは明確かつ具体的な情報で、どこかになんらかの方法で保存する必要があります。.NET 空間最初の O/RM である NHibernate では、この情報を XML ファイルに保存します。EF の場合は、当初 EDMX ファイルを使って同じアプローチを取り、Visual Studio 内部から情報を管理する優れたデザイナーを追加しました。Code First は、属性または API (使いやすく機能豊富な API) を使用して、クラスのプロパティを列やテーブルにマッピングします。

EF チームが数か月前に投稿したブログ記事では、データ モデルを保存する方法として EF7 では Code First のみをサポートすることについて明快に解説しています (全文は bit.ly/1sLM3Ur (英語) で確認できます)。 このブログ記事では、Code First が実際に行っていることをわかりやすく表す名前として、「コードベース モデリング」という表現を使用しています。まったく同感です。

DDD の概要

DDD はソフトウェア開発アプローチの 1 つです。当初 DDD は、途方もないレベルの複雑さ (膨大な数のビジネス ルールとエンティティ) を管理するために体系的に適用する一連のルールとして考案されました。DDD は、数百以上のルールやエンティティを含む非常に大規模なシステムでその威力を発揮します。ただし、開発者やアーキテクトにとっては、もっと単純なシナリオでも効果があります。簡単に言うと、DDD のある部分は、どのようなソフトウェア プロジェクトでも適用しない理由はありません。適用するのは、戦略的設計の部分です。戦略的設計は、よく知られたいくつかの手法を利用する際にアプリケーションの中心的な役割を果たします。 それは、ユビキタス言語、境界コンテキスト、およびコンテキスト マップを利用する場合です。これらの分析パターンは、最終的にアプリケーションが使用することになる実際のクラスやデータベース テーブルとはほぼ関係がありません。その最終目標は、効率のよいコードを作成できるようにすることです。DDD の戦略的設計パターンは、ビジネス ドメインを分析して、最終的なシステムの最上位アーキテクチャを予測することが目的です。図 1 に、E コマース ソリューションとして可能性のある最上位アーキテクチャを示します。各ブロックは境界コンテキストを表します。この境界コンテキストは、分析中に特定され、開発を高速化する目的で導入されます。

境界コンテキストによる最上位アーキテクチャのサンプル
図 1 境界コンテキストによる最上位アーキテクチャのサンプル

分析によって特定される各境界コンテキストは、それぞれ独自のビジネス言語、独自のソフトウェア アーキテクチャ (テクノロジを含む)、および他の境界コンテキストとのリレーションシップの独自のセットを持ちます。境界コンテキストはそれぞれ独自のソフトウェア アーキテクチャを使って実装できます。このソフトウェア アーキテクチャには、関与するチームの数やスキル、予算、時間的制約、および関係者が抱えるその他の懸念事項 (既存のソフトウェア ライセンス数、コスト、専門知識、ポリシーなど) に最適なアーキテクチャを使用します。DDD では、境界コンテキストの要素を構築する効率的な方法として、階層型アーキテクチャを明示しています。

階層型アーキテクチャにおけるドメイン モデル

図 2 は、階層型アーキテクチャの要点を示しています。このアーキテクチャには、プレゼンテーション層からインフラストラクチャ層までの 4 層構造で、中間にはアプリケーション層とドメイン層があります。簡単に言えば、これはプレゼンテーション層、ビジネス層、データ層から成る著名な 3 層アーキテクチャを一般化した形式で、さらにユース ケース ロジックとドメイン ロジックを明確に分離するアーキテクチャです。ユース ケース ロジックとは、プレゼンテーションで検討するユース ケースに応じて変わるロジックです。ドメイン ロジックとは、ビジネスを行う具体的な方法に固有のロジックで、すべてのユース ケースとプレゼンテーション層に共通のロジックです。

階層型アーキテクチャのスキーマ
図 2 階層型アーキテクチャのスキーマ

インフラストラクチャ層には、ユース ケースの実装とサポート、およびドメイン エンティティの状態の保存に必要なものをすべて含みます。したがって、データベースへの接続文字列を認識するコンポーネントは、インフラストラクチャ層に含まれます。

DDD アプローチの中心となるのが「ドメイン モデル」という考え方です。 簡単に言えば、ドメイン モデルとは、ビジネス ドメイン全体を表現するために作成するソフトウェア モデルです。つまり、対応するドメインを処理するために、ソフトウェアを使って実現できるすべてです。通常、ドメイン モデルにはエンティティ、イベント、および値オブジェクトが含められ、あるエンティティと値オブジェクトが分離できない単位として連携して機能する場合もあります。DDD では、これを「集約」と呼びます。集約には根底となる集約ルートがあります。永続化が行われるのは集約ルートのレベルです。通常、集約に含まれるすべてのエンティティと値オブジェクトが永続化を担当するのが集約ルートです。

エンティティと値の型の集約は、どのようにコーディングすればよいでしょう。 それは、使用するプログラミング パラダイムに応じて変わります。大半のドメイン モデルは、オブジェクト指向モデルです。オブジェクト指向モデルでは、エンティティがプロパティとメソッドをもつクラスで、値オブジェクトが変更不可のデータ構造になります。関数型言語と変更不可のデータ構造を使用することもできます。ただし、特定の種類のビジネス ドメインに限定されます。

Code First は、厳密には、データ アクセス タスクのパフォーマンスに関連する明確なテクノロジです。Code First の最も特徴的な側面は、アプリケーションが使用するテーブルの基になるスキーマとデータをクラスで表現することです。この場合、アプリケーションが使用するデータと、アプリケーションがリレーショナル テーブルを使って永続化するデータは同じものになるでしょうか。 別の問い方をすると、リレーショナル データベースのテーブルとマップするために Code First が使用するクラスのセットは、アプリケーションのドメイン モデルと同じものになるでしょうか。 答えは、ほとんどの場合、同じにはなりません。ただし、いつものことですが、ソフトウェア アーキテクチャについて言えば状況によって異なります。

階層型アーキテクチャにおけるドメイン モデル

Code First は、DDD に関連付けられることがあります。それは、クラスを利用してアプリケーションのデータをモデル化できるためです。場合によっては、ドメインのビジネス ロジックと永続化の問題を両方扱うクラスが 1 セットあれば十分ですが、一般論としては、ドメイン モデルと永続化モデルは別物です。ドメイン モデルは、システムのドメイン ロジックを表現し、ビジネス ルールを実装するためのソフトウェア モデルです。これはオブジェクト指向モデルにも、関数モデルや、ヘルパー クラスから公開される静的メソッドのプレーンなコレクションにもなり得ます。

DDD のポイントは、永続化の問題をドメイン モデルから切り離し、ドメイン モデルの設計では、ビジネス エンティティに含まれるデータやそこで管理するデータよりも、ビジネス エンティティが行う対象 (およびその使用方法) に重点を置くことです。動作を中心に据えるアプローチによって、途方もないレベルの複雑さを、コードで効率的に対応できるレベルまで細分化します。簡単な例としてスポーツの試合を考えます (図 3 参照)。

ドメイン モデルのエンティティにおける動作とデータの比較
図 3 ドメイン モデルのエンティティにおける動作とデータの比較

得点を争う状況での試合 (Match) エンティティの動作を表現するには、試合開始 (Start)、試合終了 (Finish)、ゴール (Goal)、タイムアウト (Timeout) のような、具体的なシナリオで意味のあるアクションをモデル化することになります。このようなメソッドにすべてのビジネス ルールを実装し、プログラムではエンティティの現在状態で実行可能なアクションだけを実行します。たとえば、タイムアウトによって現在中断中の試合 ( Match インスタンス) のメソッド Goal が呼び出されると、例外がスローされることになります。Match エンティティの内部状態には、このようなプロパティをすべて含めます。通常、これらのプロパティを純粋なリレーショナル モデルのエンティティに関連付けます。ただし、プロパティが読み取り専用で、メソッド内部でのみ更新される場合は除きます。

ドメイン モデルに含まれるすべてのクラスを永続化する必要はありません。プロパティは、すべてを永続化しても、いくつか選択して永続化してもかまいません。したがって、一般的には Code First とドメイン モデリングとは関係してません。ただし、テーブルの列にプロパティをマップする Code First の API を使用して、ドメイン モデルから必要なクラスを永続化することはできます。この方法で、ビジネスと永続化のニーズ両方に対応するドメインに対してモデルを 1 つ用意できます。

プライベート セッターの問題

ドメイン モデリングの観点では、エンティティが従うビジネス ワークフローのみを操作します。このワークフローは、ドメインの専門家が作成します。試合の得点の例に戻ると、ワークフローという考え方は、試合の状態や得点をプログラムで設定するビジネス ルールとは一致しない可能性があります。事実、状態と得点は、ワークフローが進むにつれて変化していきます。同様に、既定のコンストラクターは必ずパラメーターを受け取ります。それは、Match エンティティが、試合を行うチームの名前や、試合を競技会に合理的に関連付ける ID などの重要な情報を返すことになるためです。しかし、ビジネスと永続化に 1 つのモデルを使用する場合は、パラメーターを受け取らないコンストラクターが必要になります。このコンストラクターがなければ、EF ではクエリ後に型のインスタンスを返すことができなくなります。

他にも、考慮が必要なことがあります。EF がクエリを実行し、Match クラスのインスタンスを返す場合、この返されたインスタンスに、データベースの情報と整合性のある状態を保存するために、すべてのプロパティのセッターにアクセスする必要があります。EF では合理的と考えられるこの要件は、ドメイン モデルの設計ルールと矛盾します。一般的には、ドメイン モデルのエンティティに状態を強制的に含める方法が存在しなければなりませんが、ほとんどの場合、その方法は内部で利用できる必要はあっても、その層外部のコードからパブリックにアクセスできてはなりません。これはドメイン サービスの目的の 1 つです。ドメイン サービスは、ドメインと共に、図 2 のドメイン層を形成します。Code First を使用する場合は、セッターを public 以外 (internal、protected、または private) でマークし、パブリックには可視にならない既定のコンストラクターを追加することで、同じことを実現できます。EF では、プライベート メンバーにアクセスして、状態を強制 (リフレクションを利用) する方法もありますが、ドメイン API のパブリック クライアントにはありません。ただし、パブリック クライアント自体がリフレクションを利用するようになればそれは変わります。

まとめ

Web を見ると Code First と DDD を関連付けて説明している資料を目にすることは珍しくはありません。Code First には、テーブルのセットに明示的にマッピングされるオブジェクト指向モデルを永続化する目的があります。概念的には、ドメイン モデルはまったく別物で、所属する層が異なることもあります。ただし、Code First API の一部の特定の機能 (プライベート セッターとコンストラクターを扱う機能) により、場合によっては 1 つのオブジェクト指向モデルを使用することができます。このモデルに動作とビジネス ルールを含めることで、リレーショナル データベースに簡単に永続化できるようになります。


Dino Esposito は、『Microsoft .NET: Architecting Applications for the Enterprise』(Microsoft Press、2014 年) および『Modern Web Applications』(Microsoft Press、2016 年) の著者です。JetBrains の .NET および Android プラットフォームのテクニカル エバンジェリストでもあります。世界各国で開催される業界のイベントで頻繁に講演しており、software2cents@wordpress.com (英語) や Twitter (@despos、英語) でソフトウェアに関するビジョンを紹介しています。

この記事のレビューに協力してくれたマイクロソフト技術スタッフの Jon Arne Saeteras に心より感謝いたします。