質問
3 層に分割されたアプリケーションがあるとします。GUI、ビジネス ロジック、データ アクセス。ビジネス ロジック層では、ビジネス オブジェクトを次のように記述しました。ゲッター、セッター、アクセサーなど...あなたはその考えを理解しています。ビジネス ロジック層へのインターフェイスにより、ビジネス ロジックの安全な使用が保証されるため、呼び出すすべてのメソッドとアクセサーは入力を検証します。
これは、信頼できるインターフェイスがきちんと定義されているため、初めて UI コードを記述する場合に最適です。
しかし、ここで厄介な部分が発生します。データ アクセス層の作成を開始すると、ビジネス ロジックへのインターフェイスがニーズに対応できなくなります。非表示になっている/非表示に使用されているフィールドを設定するには、より多くのアクセサーとゲッターが必要です。ここで、ビジネス ロジックのインターフェイスを侵食する必要があります。UI レイヤーにはビジネス設定がありませんが、UI レイヤーからフィールドを設定できるようになりました。
データ アクセス層に必要な変更により、ビジネス ロジックへのインターフェイスが侵食され、ビジネス ロジックに無効なデータを設定することさえ可能になってしまいました。したがって、このインターフェースは安全な使用を保証しません。
問題を十分に明確に説明できたと思います。インターフェイスの侵食を防ぎ、情報の隠蔽とカプセル化を維持しながら、異なる層間で異なるインターフェイスのニーズに対応するにはどうすればよいでしょうか?
解決
私の質問の理解が正しければ、あなたはドメイン モデルを作成しており、データベース内のレコードとドメイン オブジェクトの間をマッピングするオブジェクト リレーショナル マッパーを作成したいと考えていることになります。ただし、オブジェクトのフィールドの読み書きに必要な「配管」コードによってドメイン モデルが汚染されるのではないかと心配しています。
一歩下がって考えると、データ マッピング コードを配置する場所には基本的に 2 つの選択肢があります。ドメイン クラス自体内か、外部マッピング クラス内です。最初のオプションはアクティブ レコード パターンと呼ばれることが多く、各オブジェクトがそれ自体を永続化する方法を知っており、ビジネスに関連しないフィールドを公開する必要なくマッピングを実行できるように内部構造に十分にアクセスできるという利点があります。
例えば
public class User
{
private string name;
private AccountStatus status;
private User()
{
}
public string Name
{
get { return name; }
set { name = value; }
}
public AccountStatus Status
{
get { return status; }
}
public void Activate()
{
status = AccountStatus.Active;
}
public void Suspend()
{
status = AccountStatus.Suspended;
}
public static User GetById(int id)
{
User fetchedUser = new User();
// Lots of database and error-checking code
// omitted for clarity
// ...
fetchedUser.name = (string) reader["Name"];
fetchedUser.status = (int)reader["statusCode"] == 0 ? AccountStatus.Suspended : AccountStatus.Active;
return fetchedUser;
}
public static void Save(User user)
{
// Code to save User's internal structure to database
// ...
}
}
この例では、Name と AccountStatus を持つ User を表すオブジェクトがあります。おそらく変更が有効なステータス遷移であることを確認したいため、ステータスを直接設定することは許可したくないため、セッターはありません。幸いなことに、GetById および Save 静的メソッドのマッピング コードには、オブジェクトの名前およびステータス フィールドへの完全なアクセス権があります。
2 番目のオプションは、マッピングを担当する 2 番目のクラスを用意することです。これには、ビジネス ロジックと永続性に関するさまざまな懸念事項が分離されるという利点があり、設計をよりテストしやすく柔軟にすることができます。このメソッドの課題は、名前フィールドとステータス フィールドを外部クラスに公開する方法です。いくつかのオプションは次のとおりです。1.リフレクション(オブジェクトのプライベートパーツを深く掘り下げることに不安がない)を使用します2。特別に名前を付けたパブリック セッターを提供します (例:「プライベート」という言葉を付けてプレフィックスし、誰も誤って使用しないことを願っています3。言語がサポートしている場合は、セッターを内部に作成しますが、データ マッパー モジュールへのアクセスを許可します。例えば。.NET 2.0 以降では InternalsVisibleToAttribute を使用するか、C++ ではフレンド関数を使用します。
詳細については、Martin Fowler の古典的な本「Patterns of Enterprise Architecture」をお勧めします。
ただし、警告として、独自のマッパーを作成する前に、nHibernate や Microsoft の Entity Framework などのサードパーティのオブジェクト リレーショナル マッパー (ORM) ツールの使用を検討することを強くお勧めします。私は 4 つの異なるプロジェクトに取り組んできましたが、さまざまな理由から独自のマッパーを作成しましたが、エンド ユーザーに価値を提供するコードを作成する代わりに、マッパーの保守と拡張に多くの時間を浪費することが非常に簡単です。私はこれまでに 1 つのプロジェクトで nHibernate を使用してきました。最初はかなりの急な学習曲線がありましたが、初期に投入した投資はかなりの成果を上げました。
他のヒント
これは古典的な問題であり、ドメイン モデルをデータベース モデルから分離する必要があります。これを攻撃する方法はいくつかありますが、私の意見では、プロジェクトの規模によって異なります。他の人が言ったように、リポジトリパターンを使用することもできます。.net または Java を使用している場合は、次を使用できます NHibernate または 休止状態.
私がやっていることは使用することです テスト駆動開発 そのため、最初に UI レイヤーとモデル レイヤーを作成し、データ レイヤーをモックします。そのため、UI とモデルはドメイン固有のオブジェクトを中心に構築され、その後、これらのオブジェクトをデータ レイヤーで使用しているテクノロジにマッピングします。データベースにアプリの設計を決定させ、最初にアプリを作成し、後でデータについて考えるのは非常に悪い考えです。
ps、質問のタイトルは少し誤解を招きます
@アイス^^熱:
データ層がビジネス ロジック層を認識すべきではないというのはどういう意味ですか?ビジネス オブジェクトにデータを入力するにはどうすればよいでしょうか?
UI はビジネス層の ServiceClass にサービスを要求します。つまり、必要なパラメーター データを持つオブジェクトによってフィルターされたオブジェクトのリストを取得します。
次に、ServiceClass はデータ層内のリポジトリ クラスの 1 つのインスタンスを作成し、GetList(ParameterType filters) を呼び出します。
次に、データ層がデータベースにアクセスしてデータを取得し、それを「ドメイン」アセンブリで定義された共通形式にマッピングします。
BL はこのデータを処理する必要がないため、データを UI に出力します。
次に、UI は項目 X を編集しようとします。アイテム (またはビジネス オブジェクト) をビジネス層のサービスに送信します。ビジネス層はオブジェクトを検証し、問題がなければ、保存するためにデータ層に送信します。
UI はビジネス層のサービスを認識し、ビジネス層もデータ層について認識します。
UI はユーザー データ入力とオブジェクト間のマッピングを担当し、データ層はデータベース内のデータとオブジェクト間のマッピングを担当します。ビジネス層は純粋にビジネスのままです。:)
インターフェースを侵食しないので、解決策になる可能性があります。次のようなクラスができると思います。
public class BusinessObjectRecord : BusinessObject
{
}
私は常に次の内容を含む別のアセンブリを作成します。
- 多数の小さなインターフェイス (ICreateRepository、IReadRepository、IReadListRepsitory など)リストはさらに続きますが、そのほとんどはジェネリックに大きく依存しています)
- IReadRepository を継承する、IPersonalRepository などの多くの具体的なインターフェイスについては、おわかりでしょう。
小さなインターフェイスだけでは説明できないものはすべて、具体的なインターフェイスに入れます。
IPersonalRepository を使用してオブジェクトを宣言している限り、クリーンで一貫したインターフェイスを使用できます。しかし、驚くべきことに、f.x を使用するクラスを作成することもできるということです。コンストラクターに ICreateRepository が含まれているため、コードは非常にファンキーな処理を非常に簡単に実行できるようになります。ここにはビジネス層のサービス用のインターフェイスもあります。 - 最後に、すべてのドメイン オブジェクトを追加のアセンブリに貼り付けます。これは、コード ベース自体をもう少しクリーンにして、より疎結合にするためです。これらのオブジェクトにはロジックはなく、3 つ以上のレイヤーすべてのデータを記述する一般的な方法にすぎません。
ところで。データ層に対応するためにビジネス ロジック層でメソッドを定義するのはなぜでしょうか?
データ層は、ビジネス層の存在を知る必要さえありません。
データ層がビジネス ロジック層を認識すべきではないというのはどういう意味ですか?ビジネス オブジェクトにデータを入力するにはどうすればよいでしょうか?
私はよくこれをします:
namespace Data
{
public class BusinessObjectDataManager
{
public void SaveObject(BusinessObject object)
{
// Exec stored procedure
{
}
}
問題は、ビジネス層がより多くの機能をデータ層に公開する必要があり、この機能を追加すると、UI 層に多くの機能が公開されることになるということでしょうか?私があなたの問題を正しく理解しているのであれば、単一のインターフェースで多くのことを満足させようとしすぎていて、それが乱雑さを引き起こしているだけのように思えます。なぜビジネス層に 2 つのインターフェイスを持たないのでしょうか?1 つは、UI 層用のシンプルで安全なインターフェイスです。もう 1 つは、データ層の下位レベルのインターフェイスです。
この 2 つのインターフェイスのアプローチは、UI レイヤーとデータ レイヤーの両方に渡す必要があるオブジェクトにも適用できます。
public class BusinessLayer : ISimpleBusiness
{}
public class Some3LayerObject : ISimpleSome3LayerObject
{}
インターフェイスを次の 2 つのタイプに分割することもできます。
- ビュー インターフェイス -- UI との対話を指定するインターフェイスです。
- データ インターフェイス -- データとのやり取りを指定できるインターフェイスです。
次のように、両方のインターフェイスのセットを継承して実装することができます。
public class BusinessObject : IView, IData
このようにすると、データ レイヤーでは IData のインターフェイス実装のみを確認する必要があり、UI では IView のインターフェイス実装のみを確認する必要があります。
使用したいと思われるもう 1 つの戦略は、オブジェクトが UI またはデータ レイヤーで単に消費されるようにこれらのレイヤーでオブジェクトを構成することです。
public class BusinessObject : DomainObject
public class ViewManager<T> where T : DomainObject
public class DataManager<T> where T : DomainObject
これにより、ビジネス オブジェクトは UI/ビュー レイヤーとデータ レイヤーの両方を認識しなくなります。
私は、本筋に逆らう私の習慣を続けて、なぜこのような恐ろしく複雑なオブジェクト レイヤーを構築しているのかを疑うべきだと言いたいと思います。
多くの開発者はデータベースをオブジェクトの単純な永続化レイヤーとして考えており、それらのオブジェクトに必要な CRUD 操作のみに関心があると思います。オブジェクトとリレーショナル モデルの間の「インピーダンスの不一致」に多大な労力が費やされています。ここにアイデアがあります:試すのをやめてください。
データをカプセル化するストアド プロシージャを作成します。データベースと対話するコードから、必要に応じて結果セット、DataSet、DataTable、SqlCommand (または java/php/whatever に相当するもの) を使用します。それらのオブジェクトは必要ありません。優れた例は、SqlDataSource を .ASPX ページに埋め込むことです。
自分のデータを誰からも隠そうとしないでください。開発者は、いつ、どのように物理データ ストアと対話するかを正確に理解する必要があります。
オブジェクト リレーショナル マッパーは悪魔です。使用をやめてください。
エンタープライズ アプリケーションの構築は、多くの場合、複雑さを管理する作業になります。物事をできるだけシンプルに保つ必要があります。そうしないと、システムが完全に保守不能になってしまいます。ある程度の結合 (いずれにせよ、どのアプリケーションにも固有のもの) を許可する場合は、ビジネス ロジック層とデータ アクセス層の両方を削除し (ストアド プロシージャに置き換える)、それらのいずれも必要なくなります。インターフェース。