2

私はここしばらく (4 か月) Yii Framework で開発を行ってきましたが、これまでに MVC でいくつかの問題に遭遇したので、経験豊富な開発者と共有したいと思います。複雑さのレベルをリストすることで、これらの問題を提示します。

[レベル 1] CR (更新の作成) フォーム. まず、フォームがたくさんあります。各フォーム自体がモデルであるため、それぞれにいくつかの検証ルール、いくつかの属性、および属性に対して実行するいくつかの操作があります。多くの場合、これらの各フォームは、単一のアクティブなレコードオブジェクトを使用してデータベース内のレコードの更新作成の両方を行います。 -> したがって、このレベルの複雑さでは、フォームは

  • 開いたとき、

    • db から db に適したデータを人間にわかりやすい方法で表示できるようにする

    • アクティブなレコード オブジェクトの属性を持つすべてのフォーム フィールドを表示できます。db テーブルからの列の追加、削除、変更は、フォームの表示に影響を与える必要があります。

  • 保存するとき、データを取得する前に、人間に優しいデータをデータベースに適したデータにフォーマットできます

  • が検証するとき、アクティブなレコード オブジェクトによって適用される基本的な検証を実行できますが、一部のビジネス ルールを満たすために他の検証も実行する必要があります。

  • 検証が失敗した場合、属性に加えられた変更とデータベースに加えられた変更をロールバックし、最初に入力したデータをユーザーに提示できます。

[レベル 2] 拡張 CR フォーム。異なるテーブルのレコードを一括で作成・更新できるフォームです。それだけでなく、フォームがそのレコードの 1 つを作成/更新するかどうかは、他の条件 (より多くのビジネス ルール) に依存する場合があるため、フォームはテーブル A、B のレコードを更新できるが D は更新できない場合があり、A のレコードを更新する場合もあります。 ,D であり、B ではない -> したがって、このレベルの複雑さでは、フォームは次のことを行う必要があることがわかります。

  • 【レベル1】を満たすことができる

  • 特定のレコードを条件付きで作成/更新したり、特定のレコードの特定の列を条件付きで作成/更新したりできます。

[レベル 3] モデルのツリー。アプリケーションにおけるフォームの役割は、多くの点で、ユーザーがアプリケーションと対話できるようにするポートです。要求を満たすために、このポートは他の多くのオブジェクトと対話し、さらに多くのオブジェクトと対話します。これらのオブジェクトのいくつかは、モデルとして見ることができます。Active Record はモデルですが、Mailer もモデルになることができ、RobotArm も同様です。これらのモデルは、相互に使用してユーザーの要求を満たします。各モデルは独自の操作を実行でき、ツリー全体がエラー/失敗の場合に加えられた変更をロールバックできる必要があります。

誰かがこれらの問題に遭遇したか、解決できましたか?

モデル属性を ModelAttribute オブジェクトにカプセル化して、クライアント、サーバー、およびデータベースの層全体に存在するようにするなど、多くのことを考え出しました。

また、モデルのツリーにオブザーバーを与えて観察し、観察されたモデルにエラーが発生したときに変更をロールバックするように通知する必要があると考えました。しかし、複数のオブザーバーが存在できる場合、ノードが親のオブザーバーを使用するが、その子に別のオブザーバーを与えるとどうなるでしょうか。

エンジニア、開発者、Rails、Yii、Zend、ASP、JavaEE、その他の MVC 関係者は、科学のためにこのディスカッションに参加してください。

-- teresko の応答への更新: ---
@teresko 実際には、サービスを作業単位内の実行に組み込み、作業単位が新規/更新/削除を気にしないようにするつもりでした。作業単位内の各オブジェクトは、その状態を担当し、独自の commit() および rollback() を実装する必要があります。エラーが発生すると、作業ユニットはすべての変更を最新の登録済みオブジェクトから最も古い登録済みオブジェクトにロールバックします。これは、データベースを扱うだけでなく、メーラー、パブリッシャーなどを扱うことができるためです。そうでない場合、ツリーは正常に実行されます。 、最も古い登録オブジェクトから最も新しい登録オブジェクトに commit() を呼び出します。このようにして、メーラーはメールを保存し、コミット時に送信できます。

データ マッパーを使用することは素晴らしいアイデアですが、データベース内の列がデータ マッパーとドメイン オブジェクトと一致することを確認する必要があります。さらに、他のモデルに依存する属性を持つ拡張 CR フォームまたはモデルは、検証とデータ型に関してそれらの属性を一致させる必要があります。では、属性はオブジェクトであり、モデルからモデルへと出荷されるのでしょうか? 属性は、それが変更されているかどうか、どの検証を実行する必要があるか、どのように人に優しく、アプリケーションに優しく、データベースに優しくできるかを伝えることもできます。db スキーマを更新すると、この属性が影響を受けるため、この変更を満たすために開発者がシステムを変更する必要がある例外がスローされます。

4

1 に答える 1

16

原因

問題の根本は、アクティブなレコードパターンの誤用です。AR は、基本的な CRUD 操作のみを持つ単純なドメイン エンティティを対象としています。大量の検証ロジックと複数のテーブル間の関係を追加し始めると、パターンがバラバラになり始めます。

単純化するために、アクティブ レコードはせいぜいマイナーなSRP違反です。責任が重くなり始めると、厳しい罰則が課せられるようになります。

ソリューション

レベル1:

最良のオプションは、ビジネス ロジックとストレージ ロジックを分離することです。ほとんどの場合、ドメイン オブジェクトデータ マッパーを使用して行われます。

  • ドメイン オブジェクト(ビジネス オブジェクトまたはドメイン モデル オブジェクトとも呼ばれる他の資料) は、検証と特定のビジネス ルールを処理し、それらのデータがどのように (または「もし」) 保存および取得されたかをまったく認識しません。また、ストレージ構造 (DB テーブルなど) に直接バインドされていないオブジェクトを持つこともできます。

    たとえばLiveReport、現在の売上データを表すドメイン オブジェクトがあるとします。ただし、DB に特定のテーブルがない場合があります。代わりに、Memcache、SQL データベース、および一部の外部 SOAP からデータをプールする複数のマッパーによってサービスを提供できます。また、LiveReportインスタンスのロジックはストレージとはまったく関係ありません。

  • データ マッパーは、ドメイン オブジェクトからの情報を配置する場所を知っていますが、検証やデータ整合性チェックは行いません。制約違反など、低レベルのストレージ抽象化から発生する例外を処理できると考えました。UNIQUE

    データ マッパーはトランザクションを実行することもできますが、複数のドメイン オブジェクトに対して単一のトランザクションを実行する必要がある場合は、作業単位の追加を検討する必要があります (詳細は後述)。

    より高度な/複雑なケースでは、データ マッパーはDAOやクエリ ビルダーと対話して利用できます。ただし、これは、ORM のような機能を作成することを目的とする場合に当てはまります。

    各ドメイン オブジェクトは複数のマッパーを持つことができますが、各マッパーはドメイン オブジェクトの特定のクラス (またはコードがLSPに準拠している場合はそのサブクラス) でのみ機能する必要があります。また、ドメイン オブジェクトとドメイン オブジェクトのコレクションは 2 つの別個のものであり、別個のマッパーを持つ必要があることも認識しておく必要があります。

    また、各データ マッパーが他のマッパーを含むことができるように、各ドメイン オブジェクトは他のドメイン オブジェクトを含むことができます。しかし、マッパーの場合、それは好みの問題です (私はそれが大嫌いです)。

現在の混乱を軽減できるもう 1 つの改善点は、アプリケーション ロジックがプレゼンテーション レイヤー (ほとんどの場合はコントローラー) でリークするのを防ぐことです。代わりに、マッパーとドメイン オブジェクト間の相互作用を含むservicesを使用することで大きなメリットが得られるため、モデル レイヤー用のパブリックっぽいAPI を作成できます。

基本的に、モデルの完全なセグメントをカプセル化するサービスであり、(現実の世界では - 少しの労力と調整で) さまざまなアプリケーションで再利用できます。例: RecognitionMailerまたはDocumentLibraryすべてのサービス。

また、すべてのサービスにドメイン オブジェクトとマッパーが含まれている必要はありません。非常に良い例は、前述Mailerの です。これは、コントローラーによって直接使用されるか、別のサービスによって使用される可能性が高くなります。

レベル2:

アクティブ レコード パターンの使用を停止すると、これは非常に単純な問題になります。最後の保存以降に実際に変更されたドメイン オブジェクトからのデータのみを保存するようにする必要があります。

私が見ているように、これにアプローチするには2つの方法があります。

  1. クイックアンドダーティ

    何かが変更された場合は、すべてを更新するだけです...

    私が好む方法はchecksum、ドメイン オブジェクトに変数を導入することです。この変数は、すべてのドメイン オブジェクトの変数からのハッシュを保持します (もちろん、checksumそれ自体を除く)。

    マッパーは、ドメイン オブジェクトの保存を要求されるたびに、isDirty()このドメイン オブジェクトのメソッドを呼び出し、データが変更されたかどうかをチェックします。その後、マッパーはそれに応じて行動できます。これは、いくつかの調整を加えることで、オブジェクト グラフにも使用できます (オブジェクト グラフがあまり広範ではない場合、いずれにせよリファクタリングが必要になる場合があります)。

    また、ドメイン オブジェクトが実際に複数のテーブル (または異なる形式のストレージ) にマップされる場合は、変数のセットごとに複数のチェックサムを用意するのが合理的かもしれません。マッパーはドメイン オブジェクトの特定のクラス用に既に作成されているため、既存の結合が強化されることはありません。

    PHP の場合、この ansewerにいくつかのコード例があります。

    注:実装で DAO を使用してドメイン オブジェクトをデータ マッパーから分離する場合、チェックサム ベースの検証のロジックは DAO に移動します。

  2. 作業単位

    これは問題の「業界標準」であり、 PoEAAブックにはそれを扱う章全体 (11 日) があります。

    基本的な考え方は、ドメイン オブジェクトとデータ マッパーの間で (MVC の意味ではなく、古典的な) コントローラーのように機能するインスタンスを作成することです。

    ドメイン オブジェクトを変更または削除するたびに、そのことをUnit of Workに通知します。ドメイン オブジェクトにデータをロードするたびに、Unit of Workにそのタスクの実行を依頼します。

    Unit of Workに変更を通知するには、次の 2 つの方法があります。

    • 呼び出し元の登録:変更を実行するオブジェクトは、作業単位にも通知します
    • オブジェクトの登録:変更されたオブジェクト (通常はセッターから) は、変更されたことを作業単位に通知します。

    ドメイン オブジェクトとのやり取りがすべて完了したらcommit()、Unit of Work でメソッドを呼び出します。次に、必要なマッパーを見つけて、変更されたすべてのドメイン オブジェクトを格納します。

レベル3:

この複雑な段階では、実行可能な実装は Unit of Work を使用することだけです。また、適切なロールバック句を使用して、SQL トランザクション (SQL データベースを使用している場合) の開始とコミットも担当します。

PS

「エンタープライズ アプリケーション アーキテクチャのパターン」という本を読んでください。それはあなたが切実に必要としているものです。また、Rails のようなフレームワークを使用して習得した MVC および MVC にインスパイアされたデザイン パターンに関する誤解も修正されます。

于 2012-11-22T04:32:13.670 に答える