1

例:

SalesOrder は、SalesOrderHeader と 1 つ以上の SalesOrderItems で構成されます。既存の SalesOrder を編集する場合、SalesOrderHeader を変更でき、SalesOrderItems を追加、変更、および削除できます。すべての変更は、単一のトランザクションで保存する必要があります。複数のユーザーが、オプティミスティック コンカレンシーで同時に SalesOrder を編集できます。

保存を 1 回のトランザクションで行うという要件により、SaleOrderHeader と SalesOrderItems の両方を 1 回のサービス呼び出しで通信することが推奨されると思います。子データをその親と一緒にパッケージ化するということは、子データが追加、変更、または削除されるかどうかについてある程度理解する必要があるということです。

子エンティティの変更追跡は、サーバーまたはクライアントで発生する可能性があります。

サーバーでの変更追跡

この戦略の考え方は、どの SalesOrderItem が追加、変更、または削除されたかを追跡することなく、クライアントが意のままに SalesOrder を変更できるということです。SalesOrderItems の状態は、保存サービスが呼び出されたときにサーバー上で決定されます。

サーバーは、サービス呼び出し間でステートレスのままにする必要があります。これは、SalesOrder を取得してから最終的に保存するまでの間、サーバーが SalesOrder の状態に関する情報を保持できないことを意味します。変更されたオブジェクト グラフをデータベース オブジェクト グラフと比較することによって、サーバーがそのエンティティの状態を判断する場合に残された唯一のオプションです。

nHibernate には、これを実現するためのマージ機能があります。エンティティ フレームワークでは、これを追加する機能リクエストが最も多く投票されました。GraphDiffと呼ばれる EF 用のオープン ソース実装もあります。

サービスの設計と使用が非常に簡単になるため、これは理論的には素晴らしいことのように思えます。ただし、この戦略には 2 つの大きな問題があると思います。1つ目はパフォーマンスです。保存するたびに、オブジェクト グラフ全体を送り返す必要があります。SalesOrderItem が変更されたかどうかにかかわらず、返送する必要があります。そうしないと、サーバーは削除されたと見なします。2 番目の問題はさらに重大で、並行性に関係しています。ユーザー 1 が SalesOrderItem を SalesOrder に追加し、ユーザー 2 が同じ SalesOrder に変更を加えた場合、ユーザー 2 が保存すると、サーバーは、ユーザー 1 によって追加された SalesOrderItem がユーザー 2 のオブジェクト グラフに含まれていなかったため、削除する必要があると想定します。サーバー側の変更追跡の実装でこれを防ぐ方法がわかりません。

クライアントでの変更追跡

もう 1 つの方法は、クライアントにエンティティの変更を追跡させ、保存サービスを呼び出すときにその状態を伝えることです。利点の 1 つは、クライアントが変更されていない子エンティティを送信する必要がないことです。これはパフォーマンスに役立ちます。欠点は、追加、変更、または削除されたかどうかを追跡するために、すべてのエンティティに「ObjectState」の行に沿って何かという名前の追加のプロパティが必要になることです。これにより、サーバー上のエンティティ モデルが非常に煩雑になり、ビジネス ドメインとは関係のない問題でいっぱいになります。これはまた、サービスのさまざまなコンシューマーにこの状態を維持する責任を負わせます。もう 1 つの問題は、削除されたエンティティの処理が困難になることです。SalesOrderHeader は、削除された SalesOrderItems のリストを維持する必要がありますか? または、SalesOrderItems に削除済みの状態を割り当てて、クライアント UI で除外する必要がありますか?

ブリーズJavaScript ライブラリにはクライアント側のエンティティ追跡の独自の実装があることは知っていますが、その実装にはクライアント側とサーバー側の両方のコンポーネントが必要であることが懸念されます。サービスレイヤーは、どちらの側で使用するテクノロジーを分離すべきではないでしょうか? 非 JavaScript クライアントが私のサービスを使用したい場合はどうすればよいですか?

質問

これは、大部分のサービス実装で対処する必要がある一般的なシナリオだと思います。私は何か間違った仮定をしたことがありますか、それとも私は何か異常または普通のことをしていますか? どのような戦略を実行しましたか? 合理的な代替手段はありますか?

4

2 に答える 2

2

完全な開示: 私はBreezeと協力しており、クライアントでの変更追跡が最適だと思います。クライアントの変更追跡により、ステートレス サーバーが可能になり、クライアントとサーバー間のトラフィックが減少し、オフラインでの使用が可能になります。

Breezeでは、あなたが言及した「ObjectState」はEntityAspectと呼ばれ、各エンティティには1つありますが、ドメインモデルの一部ではありません。サーバー側のエンティティには EntityAspect は必要ありませんが、サーバー側のサービスは、クライアントからのエンティティ状態情報を処理する方法を認識している必要があります。

基本的に、サービスはクライアントからの情報に基づいてエンティティを作成、更新、または削除する必要があります。これをすべて実行する既存の Breeze 用のサーバー側バックエンドがあります (.NET (EF および NHibernate)JavaPHPNode、およびRubyで) が、独自に作成することもできます。サーバーは、クライアントとの通信方法を知っている必要があります。

SalesOrderを更新し、新しい を追加したとしましょうSalesOrderItem。Breeze クライアントは、次のような保存バンドルを送信します。

{
"entities": [
    {
        "Id": 123,
        "Title": "My Updated Title",
        "OrderDate": "2014-08-03T07:00:00.000Z",
        "entityAspect": {
            "entityTypeName": "SalesOrder:#My.DomainModel",
            "entityState": "Modified",
            "originalValuesMap": {
                "Title": "My Original Title"
            },
            "autoGeneratedKey": {
                "propertyName": "Id",
                "autoGeneratedKeyType": "Identity"
            }
        }
    },
    {
        "Id": -1,
        "SalesOrderId": 123,
        "ProductId": 456,
        "Quantity": 11,
        "entityAspect": {
            "entityTypeName": "SalesOrderItem:#My.DomainModel",
            "entityState": "Added",
            "originalValuesMap": {
            },
            "autoGeneratedKey": {
                "propertyName": "Id",
                "autoGeneratedKeyType": "Identity"
            }
        }
    }
]
}

ここでは、ID# 123 の SalesOrder が変更されています (タイトルが変更されています)。には、以前のタイトルが何であったかを示すがentityAspect含まれています。 サーバーは、既存の SalesOrder を新しい値で更新する必要があります。変更を適用する前に、サーバーがデータベースから既存の SalesOrder を照会する必要があるかどうかは、実装に依存します。originalValuesMap

新しい SalesOrderItem が追加されました。クライアントで一時 ID -1 が作成されました。サーバーは、新しい SalesOrderItem を作成して保持し、その実際の ID を生成する必要があります。

サーバーからの応答には、作成および更新されたエンティティと、クライアントがそれらを置き換えることができるように、サーバーによって生成されたキーがクライアント側の一時的なキーにマップされることを示す KeyMapping 情報が含まれている必要があります。

変更の追跡は単純な問題ではありませんが、Breeze は難しい部分を解決しようとします。

于 2014-08-21T21:35:13.607 に答える
1

スティーブの答えに便乗したいと思います。

明確にする必要があります。リレーショナル データ モデルでオーダー グラフ (別名「オーダー集計」) トランザクションを実装する責任は、開発者にあります。BreezeJS (および .NET サーバー用の Breeze ヘルパー) を使用すると容易になりますが、機能させる必要があります。

これを機能させるための鍵は、集約内のエンティティに対するすべての変更に、集約のルート要素である Order を含めることです。OrderItem を追加、削除、または変更する場合は、同時に Order を変更するようにしてください

どのように?オーダーの同時実行プロパティ (例: rowVersion) を増やし、Breeze がこれが同時実行プロパティであることを認識していることを確認します。

Order 集計の一貫性を確保する場合は、ルート エンティティの楽観的同時実行を実装する必要があります。

これで、他の誰かが Order 集計の一部に変更を加えたかどうかを検出できます。これは、Order の変更、または OrderItem の 1 つの追加/変更/削除である可能性があります。

変更した Order 集計を保存するときに、すべての OrderItem を変更セットに含める必要はありません。追加/変更/削除された OrderItems のみを含める必要があります。

もちろん、あなたが保存する前に、他のユーザーが Order 集計に変更を加える可能性があります。自分のものを保存しようとすると、オプティミスティック コンカレンシー エラーで保存が失敗します。

Order のオプティミスティック コンカレンシー エラーが検出されたら、クライアントが注文集計全体 (Order とそのすべての OrderItems) をキャッシュから削除したことを確認してから、集計を再フェッチします。ルート Order エンティティを再フェッチして開始するだけではありません。そのアイテムをいじります。キャッシュから集計全体を削除してから、再度取得してください (注文とそのアイテム)。

全員がこのプロトコルに従えば、サーバー上で問題なく使用できます。

于 2014-08-23T06:35:42.067 に答える