ビジネス層構造、どのように構築しますか?
-
01-10-2019 - |
質問
私は開発上の選択肢として NTier の大ファンですが、もちろん、すべてのシナリオに適合するわけではありません。
現在、新しいプロジェクトに取り組んでおり、普段の仕事のやり方を少し変えて、クリーンアップできるかどうかを試しています。私は非常に悪い子で、プレゼンテーション層にあまりにも多くのコードを入れてきました。
私の通常のビジネス層構造は次のとおりです (その基本的なビュー)。
- 仕事
- サービス
- Fooコンポーネント
- フーヘルパーズ
- Fooワークフロー
- バーコンポーネント
- バーヘルパーズ
- Bahワークフロー
- Fooコンポーネント
- 公共事業
- 一般
- 例外ハンドラー
- 輸入業者
- 等...
- サービス
上記により、それぞれのヘルパーを介して Foo オブジェクトと Bah オブジェクトを直接保存するための優れたアクセス権が得られました。
XXXHelpers を使用すると、それぞれのオブジェクトの保存、編集、ロードにアクセスできますが、子オブジェクトを含むオブジェクトを保存するロジックはどこに配置すればよいでしょうか。
例えば:
以下のオブジェクトがあります (私が知っているオブジェクトはあまり良いものではありません)
- 従業員
- 従業員詳細
- 従業員メンバーシップ
- 従業員プロフィール
現在、これらすべてをプレゼンテーション層で構築してからヘルパーに渡しますが、これは間違っていると感じています。データはビジネス層のどこかのプレゼンテーションの上の単一ポイントに渡されて、そこで整理されるべきだと思います。
しかし、このロジックをどこに置くべきか、またそのセクターを何と呼ぶべきか、少し迷っています。このセクターは、EmployeeManager などとして公益事業の下に置かれるのでしょうか?
あなたならどうしますか?そして、これがすべて好みであることは承知しています。
より詳細なレイアウト
ワークフローには、DataRepository への直接の呼び出しがすべて含まれています。次に例を示します。
public ObjectNameGetById(Guid id)
{
return DataRepository.ObjectNameProvider.GetById(id);
}
次に、ヘルパー プロバイダーがワークフローにアクセスします。
public ObjectName GetById(Guid id)
{
return loadWorkflow.GetById(id);
}
これは、Workflowに1つの呼び出しをgetBySomePropertyに繰り返してから、他の操作を行い、さまざまな方法でデータを返すことができるヘルパーでいくつかの呼び出しを行うことができるため、重複コードを削減するためです。
DataRepository を使用してデータ モデルへの呼び出しを分離すると、モデルを別のインスタンスに交換できるようになります (これが考えられました) が、ProviderHelper は分解されていないため、現状では交換できません。残念ながらEFにハードコードしてください。アクセス テクノロジを変更するつもりはありませんが、将来的には、より優れたもの、またはクールな子供たちが現在使用しているものを代わりに実装したいと思うかもしれません。
プロジェクト名.コア
projectName.Business
- Interfaces
- IDeleteWorkflows.cs
- ILoadWorkflows.cs
- ISaveWorkflows.cs
- IServiceHelper.cs
- IServiceViewHelper.cs
- Services
- ObjectNameComponent
- Helpers
- ObjectNameHelper.cs
- Workflows
- DeleteObjectNameWorkflow.cs
- LoadObjectNameWorkflow.cs
- SaveObjectNameWorkflow.cs
- Utilities
- Common
- SettingsManager.cs
- JavascriptManager.cs
- XmlHelper.cs
- others...
- ExceptionHandlers
- ExceptionManager.cs
- ExceptionManagerFactory.cs
- ExceptionNotifier.cs
projectName.Data
- Bases
- ObjectNameProviderBase.cs
- Helpers
- ProviderHelper.cs
- Interfaces
- IProviderBase.cs
- DataRepository.cs
projectName.Data.Model
- Database.edmx
projectName.Entities (Entities that represent the DB tables are created by EF in .Data.Model, this is for others that I may need that are not related to the database)
- Helpers
- EnumHelper.cs
プロジェクト名.プレゼンテーション
(アプリケーションの呼び出し内容によって異なります)
projectName.web
projectName.mvc
projectName.admin
テストプロジェクト
projectName.Business.Tests
projectName.Data.Test
解決
興味深い質問には +1。
したがって、あなたが説明した問題は非常に一般的なものです。私は別のアプローチをとります。最初に論理層、次にユーティリティとヘルパーの名前空間について、私は完全に考慮に入れてみます。その理由については、次の記事で説明します。 2番。
まず最初に、ここで私が推奨するアプローチは、非常に一般的なエンタープライズ アーキテクチャです。これを簡単に説明しますが、そこにはさらに奥深いものがあります。それには考え方の根本的な変更が必要です。NHibernate または Entity フレームワークを使用して、オブジェクト モデルを直接クエリできるようにし、データベースとのマッピングや遅延読み込み関係などを ORM に処理させます。これを行うと、すべてのビジネス ロジックをドメイン モデル内に実装できるようになります。
まず層 (またはソリューション内のプロジェクト)。
あなたのアプリケーション.ドメイン
ドメイン モデル - 問題空間を表すオブジェクト。これらは、主要なビジネス ロジックをすべて備えた単純な古い CLR オブジェクトです。ここにサンプル オブジェクトが存在し、それらの関係がコレクションとして表現されます。この層には永続性などを扱うものは何もなく、単なるオブジェクトです。
あなたのアプリケーション.データ
リポジトリ クラス - これらは、ドメイン モデルの集約ルートの取得を処理するクラスです。
たとえば、サンプル クラスでは、Employee も参照せずに EmployeeDetails も参照したいとは考えにくいです (これは私も知っている仮定ですが、要点はおわかりでしょう - 請求書明細行がより良い例であり、通常は、請求書明細行にアクセスすることになります)個別にロードするのではなく、請求書に請求します)。そのため、集約ルートごとに 1 つのクラスを持つリポジトリ クラスは、問題の ORM を使用してデータベースから初期エンティティを取得し、クエリ戦略 (ページングやソートなど) を実装し、集約ルートを消費者。リポジトリは、現在のアクティブなデータ コンテキスト (NHibernate の ISession) を消費します。このセッションがどのように作成されるかは、構築しているアプリの種類によって異なります。
あなたのアプリケーション.ワークフロー
- YourApplication.Services と呼ばれることもありますが、これは Web サービスと混同される可能性があります。
- この層は、相互に関連する複雑なアトミック操作に関するものです。プレゼンテーション層で呼び出すものを大量に用意して結合を増やすのではなく、そのような操作をワークフローまたはサービスにラップすることができます。
- 多くのアプリケーションではこれなしで済む可能性があります。
他の層は、アーキテクチャと実装しているアプリケーションによって異なります。
YourApplication.YourChosenPresentationTier
Web サービスを使用して層を分散している場合は、ドメインとコンシューマの間で公開するデータのみを表す DTO コントラクトを作成します。ドメインからこれらのコントラクトにデータを出入りする方法を認識するアセンブラを定義します (ネットワーク経由でドメイン オブジェクトを送信することはありません!)。
この状況では、クライアントも作成しているため、プレゼンテーション層で上で定義した操作およびデータ コントラクトを使用することになり、各 DTO はビュー固有である必要があるため、おそらく DTO に直接バインドされます。
層を分散する必要がない場合は、分散アーキテクチャの最初のルールが分散しないことを思い出して、asp.net、mvc、wpf、winforms などからワークフロー/サービスとリポジトリを直接利用することになります。
これにより、データ コンテキストが確立される場所が残ります。Web アプリケーションでは、通常、各リクエストは完全に自己完結型であるため、リクエストをスコープとしたコンテキストが最適です。つまり、コンテキストと接続はリクエストの開始時に確立され、最後に破棄されます。選択した IoC/依存性注入フレームワークを取得して、リクエストごとのコンポーネントを構成するのは簡単です。
デスクトップ アプリ、WPF、または winforms では、フォームごとにコンテキストがあります。これにより、編集ダイアログでドメイン エンティティを編集しても、モデルは更新されますが、データベースには反映されません (例:キャンセルが選択されました) 他のコンテキストを妨げたり、最悪の場合、誤って永続化されることはありません。
依存関係の注入
上記のすべては、最初にインターフェイスとして定義され、具体的な実装は IoC および依存関係注入フレームワークを通じて実現されます (私の好みは Castle Windsor です)。これにより、個々の層を個別に分離、モック、単体テストできるようになり、大規模なアプリケーションでは依存関係の注入が命の恩人になります。
それらの名前空間
最後に、ヘルパー名前空間をなくす理由は、上記のモデルではヘルパー名前空間が必要ないだけでなく、ユーティリティ名前空間と同様に、怠惰な開発者にコードの一部が論理的にどこに配置されるかを考えない口実を与えてしまうからです。MyApp.Helpers.* と MyApp.Utility.* は、何らかのコードがある場合、たとえば論理的に MyApp.Data.Repositories.Customers 内に属する例外ハンドラー (おそらく、顧客参照が一意ではない例外である可能性があります)、遅延ハンドラーであることを意味します。開発者は特に考えることなく、MyApp.Utility.CustomerRefNotUniqueException に配置するだけで済みます。
まとめる必要がある共通のフレームワーク タイプのコードがある場合は、MyApp.Framework プロジェクトと関連する名前空間を追加します。新しいモデル バインダーを追加する場合は MyApp.Framework.Mvc に配置し、一般的なログ機能の場合は MyApp.Framework.Logging に配置します。ほとんどの場合、ユーティリティまたはヘルパーの名前空間を導入する必要はありません。
まとめ
表面をなぞっただけですので、何かのお役に立てれば幸いです。これが私が今日どのようにソフトウェアを開発しているかであり、意図的に簡潔にするよう努めています。詳細について詳しく説明できる場合は、お知らせください。この意見の多い記事で最後に言いたいのは、上記はかなり大規模な開発を対象としたものであるということです。メモ帳バージョン 2 や企業の電話帳を作成している場合、上記はおそらく完全にやりすぎです。
乾杯トニー
他のヒント
このページには、アプリケーションレイアウトに関する素敵な図と説明があります。Alhtoughは、アプリケーションが物理レイヤー(Seperate Project)に分割されていない記事をさらに下に示します。 エンティティフレームワークPOCOリポジトリ