マイクロサービスデータベースのシード


10

モデル(製品、ID、タイトル、価格)を制御するサービスA(CMS)と、与えられたモデルをどのように表示する必要があるかを示す必要があるサービスB(配送)とC(メール)があるとします。イベントソーシングアプローチでこれらのサービス全体で特定のモデル情報を同期するには 商品カタログがめったに変更されない(ただし変更される)と、出荷および電子メールのデータに頻繁にアクセスできる管理者がいるとします(機能例:B:display titles of products the order containedおよびC:)display content of email about shipping that is going to be sent。各サービスには独自のDBがあります。

解決策1

イベント内の製品に関する必要なすべての情報を送信します-これは以下の構造を意味しますorder_placed

{
    order_id: [guid],
    product: {
        id: [guid],
        title: 'Foo',
        price: 1000
    }
}

サービスBおよびCでは、製品情報はテーブルのproductJSON属性に格納されordersます

そのため、必要な情報を表示するために、イベントから取得されたデータのみが使用されます

問題:BとCで提示する必要のある他の情報によっては、イベントのデータ量が増える可能性があります。BとCはProductについて同じ情報を必要としない場合がありますが、イベントには両方を含める必要があります(イベントを2つに分割しない限り)。特定のデータが特定のイベント内に存在しない場合、コードはそれを使用できません- 特定の製品にオプションを追加すると、BおよびCの既存の注文に対して、イベントを更新して再実行しない限り、特定の製品は無色になります。 。

解決策2

イベント内の製品のガイドのみを送信-これは以下の構造を意味しますorder_placed

{
    order_id: [guid],
    product_id: [guid]
}

サービスBおよびCでは、製品情報はテーブルのproduct_id属性に格納されますorders

製品情報は、A/product/[guid]エンドポイントへのAPI呼び出しを実行することにより、必要に応じてサービスBおよびCによって取得されます

問題:これにより、BとCが(常に)Aに依存します。製品のスキーマがAで変更された場合、それらに依存するすべてのサービスで(突然)変更を行う必要があります。

解決策3

イベント内の製品のGUIDのみを送信-これは、order_placedの次の構造を意味します。

{
    order_id: [guid],
    product_id: [guid]
}

サービスBおよびCでは、製品情報がproductsテーブルに保存されます。まだありますproduct_idordersの複製テーブルが、そこのproductsA、BとCの間のデータは、BとCには、製品に関する情報がAとは異なる場合があります

製品情報は、サービスBとCが作成されるときにシードされ、A/productエンドポイントを呼び出す(すべての製品の必要な情報を表示する)か、Aに直接DBアクセスを実行して、所定の必要な製品情報をコピーすることにより、製品に関する情報が変更されるたびに更新されます。サービス。

問題:これにより、BとCがAに依存します(シード時)。製品のスキーマがAで変更される場合、それらに依存するすべてのサービスで変更を行う必要があります(シード時)。


私の理解から、正しいアプローチはソリューション1を使用して、特定のロジックごとにイベント履歴を更新することです(製品カタログが変更されておらず、表示される色を追加したい場合は、履歴を安全に更新して現在の状態を取得できます)製品の欠落とイベント内の欠落データの記入)または指定されたデータの不存在への対応(製品カタログが変更され、表示する色を追加したい場合、その時点で過去の指定された製品かどうかわからない色があるかどうか-前のカタログのすべての製品が黒で、イベントまたはコードを更新することで対応できると想定できます)


に関してupdating event history-イベント調達イベント履歴はあなたの真実の源であり、決して変更されるべきではなく、前進するだけです。イベントが変更された場合は、イベントのバージョン管理または同様のソリューションを使用できますが、特定の時点までイベントを再生すると、データの状態はその時点の状態になります。
いいえ

クエリ用のデータ(スキーマなど)の保存やフィールドの追加/削除などに関しては、当時のcosmosDBをそのままJSONに保存して使用しました。バージョン管理が必要なのは、イベントやコマンドだけです。クライアント(Web、モバイルなど)からのクエリに応答するデータを含むエンドポイントコントラクトと値オブジェクトも更新する必要があります。フィールドのない古いデータにはデフォルト値または空白があり、これはビジネスに適していますが、イベント履歴は変化せず、先に進むだけです。
いいえ

@つまり、つまり、updating event historyすべてのイベントを通過し、1つのストリーム(v1)から別のストリーム(v2)にコピーして、一貫したイベントスキーマを維持します。
eithed

余談ですが、コマース/ eコマースのレルムでは、価格が頻繁に変化することを考えると、前述のように価格を取得したい場合があります。ユーザーに表示される価格は、実際の注文がキャプチャされた時点で異なる場合があります。問題を解決する方法はいくつもありますが、考慮する必要のある方法です。
CPerson

@CPerson yup-価格は、イベント自体の中で渡される属性の1つである可能性があります。一方、画像のURLが(の意図を表すイベント内に存在することができるdisplay image at the point when purchase was made)、または(の意図を表すことはできませんdisplay current image as it within catalog
eithed

回答:


3

ソリューション#3は、本当に正しい考えに近いものです。

これについて考える方法:BとCはそれぞれ、必要なデータの「ローカル」コピーをキャッシュしています。B(および同様にC)で処理されるメッセージは、ローカルにキャッシュされた情報を使用します。同様に、レポートはローカルにキャッシュされた情報を使用して作成されます。

データは、安定したAPIを介してソースからキャッシュに複製されます。BとCは、同じAPIを使用する必要さえありません。彼らは、ニーズに適したフェッチプロトコルを使用します。実際には、プロバイダーとコンシューマーを制約するコントラクト(プロトコルとメッセージスキーマ)を定義します。次に、その契約のすべての消費者を任意のサプライヤーに接続できます。下位互換性のない変更には、新しい契約が必要です。

サービスは、ニーズに応じて適切なキャッシュ無効化戦略を選択します。これは、定期的にソースから変更をプルすること、または状況が変更された可能性があるという通知に応じて、または「オンデマンド」でさえ、読み取りスルーキャッシュとして機能し、データの保存されたコピーにフォールバックすることを意味する場合がありますソースは利用できません。

これにより、Aが一時的に利用できない場合でもBとCがビジネス価値を提供し続けることができるという意味で、「自律性」が得られます。

推奨資料外部のデータ、内部のデータ、Pat Helland 2005。


ええ、私はあなたがここに書いたものに完全に同意し、ソリューション3は私が適用したgotoソリューションですが、それはイベントソーシングアプローチではありません。イベントを再生する場合、必ずしも製品の現在の状態を使用します。イベントの時点の状態を使用します。もちろん、これは問題ないかもしれません(ビジネス要件によって異なります)。しかし、私たちはあるだけでなく、それらを調達イベントを必要とカタログへの変更の追跡、および依存をどのくらいのデータ保存したい場合は、私たちはより良い解決策1に戻って落ちるかもしれない
eithed

1
ソリューション#3を使用していると思います。カタログとの整合性を再生する必要がある場合は、イベントソースも同じです。再起動する必要があるのは、再起動するとき(おそらく起動時)です。起動したら、新しいイベントを確認するだけでよいので、データ量はおそらく実際の問題ではありません。ただし、それでも(必要に応じて)チェックポイントを使用するオプションがあります。つまり、「ここにイベント1,000の状態があります」なので、これを取得すると、履歴全体ではなくイベント1,001から現在までを再生するだけで済みます。 。
マイクB.

2

コンピュータサイエンスには2つの難しいことがあり、そのうちの1つはキャッシュの無効化です。

ソリューション2は絶対に私のデフォルトの位置であり、一般的に、次のシナリオのいずれかに遭遇した場合にのみ、キャッシュの実装を検討する必要があります。

  1. サービスAへのAPI呼び出しがパフォーマンスの問題を引き起こしています。
  2. サービスAがダウンしてデータを取得できなくなるコストは、ビジネスにとって重要です。

パフォーマンスの問題は、実際の主な原因です。#2を解決するには、サービスAの高可用性を確保するなど、キャッシュを使用しない多くの方法があります。

キャッシングはシステムにかなりの複雑さを追加し、推論するのが難しいエッジケースや、再現が非常に難しいバグを作成する可能性があります。あなたはまた、新しいデータが存在する場合、古いデータを提供するリスクを軽減するために持っていることができます「 -後ほど再度お試しくださいサービスAがダウンしている。」というメッセージを表示する(例えば)よりも、ビジネスの観点から非常に悪くなります

Udi Dahanによるこの優れた記事から:

これらの依存関係はゆっくりとあなたに忍び寄り、靴ひもを結びつけ、徐々に開発のペースを遅くし、システムの一部への変更が他の部分を破壊するコードベースの安定性を損ないます。それは千カットによるゆっくりとした死であり、その結果、すべてがそんなに悪くなった原因となった私たちの大きな決断は誰にも正確にはわかりません。

また、製品データの特定の時点でのクエリが必要な場合は、データが製品データベースに格納される方法(開始/終了日など)で処理する必要があり、APIで明確に公開する必要があります(有効日はデータをクエリするためのAPI呼び出しの入力です)。


1
@SavvasKleanthous「ネットワークは信頼できる」は、分散コンピューティングの誤りの1つです。しかし、その誤りに対する反応は、「他のすべてのサービスのすべてのサービスからのすべてのデータをキャッシュする」ことではありません(私はそれが少し双曲線であることを理解しています)。サービスが利用できない可能性があることを期待し、それをエラー状態として扱います。サービスAのダウンがビジネスに大きな影響を与えるというまれな状況がある場合は、(慎重に)他のオプションを検討してください。
Phil Sandler

1
@SavvasKleanthousは、(私の回答で述べたように)多くの場合、古いデータを返すことはエラーをスローするよりもはるかに悪いことを考慮します。
Phil Sandler

1
@私はこのコメントを参照していました:「ただし、カタログへの変更を追跡したい場合は、イベントのソースも必要です」。いずれにせよ、あなたは正しい考えを持っています。Productサービスは、ダウンストリームサービスではなく、時間の経過に伴う変更の追跡を担当する必要があります。
Phil Sandler

1
さらに、観察したデータを保存することは、キャッシングといくつかの類似点がありますが、同じ問題は発生しません。具体的には、無効化は必要ありません。それが発生すると、データの新しいバージョンを取得します。あなたが経験するのは遅れた一貫性です。ただし、Webリクエストを使用しても、一貫性のないウィンドウがあります(ごくわずかですが)。
Savvas Kleanthous

1
@SavvasKleanthousいずれにしても、私の主なポイントは、まだ存在しない問題を解決しようとすることではありません。特に、独自の問題とリスクをもたらすソリューションを使用することです。オプション2は最も単純なソリューションであり、ビジネス要件を満たさない場合を除いて、デフォルトの選択にする必要があります。機能する最も単純なソリューションを選択するのが(あなたが言うように)「本当に悪い」と思うなら、私たちは反対します。
Phil Sandler

2

1つのソリューションが他のソリューションよりも優れていると単純に言うのは非常に困難です。ソリューション#2と#3のどちらを選択するかは、他の要因(キャッシュ期間、一貫性の許容度など)に依存します。

私の2セント:

キャッシュの無効化は難しいかもしれませんが、問題の説明では、製品カタログはめったに変更されないことが言及されています。この事実は、製品データをキャッシングの良い候補にします

ソリューション#1(NOK)

  • データは複数のシステム間で複製されます

ソリューション#2(OK)

  • 強い整合性を提供
  • 製品サービスの可用性が高く、優れたパフォーマンスを提供する場合にのみ機能します
  • 電子メールサービスが要約を作成する場合(多くの製品を使用)、全体の応答時間が長くなる可能性があります

ソリューション#3(複雑ですが推奨)

  • 製品情報を取得するために、直接DBアクセスではなくAPIアプローチを優先する
  • 耐障害性-製品サービスが停止しても、サービスを消費することに影響はありません
  • 消費アプリケーション(発送および電子メールサービス)は、イベントが公開された直後に製品の詳細を取得します。この数ミリ秒以内に製品サービスが停止する可能性はほとんどありません。

1

一般的に言えば、これら2つのサービス間の一時的なカップリングのため、オプション2は強くお勧めします(これらのサービス間の通信が非常に安定しておらず、頻繁ではない場合)。時間的結合とは、と表現するものthis makes B and C dependant upon A (at all times)であり、Aがダウンしているか、BまたはCから到達できない場合、BおよびCはそれらの機能を実行できません。

私は個人的に、オプション1と3はどちらも有効なオプションであると考えています。

AとBとCの間の通信が非常に多い場合、またはイベントに入るのに必要なデータの量が問題になるほど大きい場合、ネットワークの負荷がはるかに少ないため、オプション3が最適なオプションです。 、およびメッセージサイズが小さくなると、操作のレイテンシが減少します。ここで考慮すべきその他の懸念事項は次のとおりです。

  1. 契約の安定性:Aを離れるメッセージの契約が頻繁に変更される場合、メッセージに多くのプロパティを配置すると、コンシューマーで多くの変更が発生します。ただし、この場合、次の理由により、これは大きな問題ではないと考えています。
    1. システムAはCMSであるとのことですが、これは、安定したドメインで作業していることを意味します。そのため、頻繁に変更が行われることはないと思います
    2. BとCが出荷されてメールで送信され、Aからデータを受信して​​いるので、改ざんせずに発見したときに追加しても安全な追加の変更が発生すると思われます。
  2. カップリング:カップリングはほとんどありません。まず、通信はメッセージを介して行われるため、データのシード中の一時的なサービス以外のサービスとその操作のコントラクトとの間にはカップリングはありません(これは回避できるカップリングではありません)。

オプション1は私が却下するものではありません。同じ量の結合がありますが、開発に関しては簡単に実行でき(特別なアクションは不要)、ドメインの安定性はこれらが頻繁に変更されないことを意味します(既に述べたように)。

私が提案する別のオプションは、3のわずかなバリエーションです。これは、起動時にプロセスを実行せず、代わりに、BおよびCで「ProductAdded」および「ProductDetailsChanged」イベントを観察します。 A.これにより、展開が速くなります(問題やバグが見つかった場合の修正が非常に簡単になります)。


2020-03-03を編集

統合アプローチを決定するとき、私には特定の優先順位があります。

  1. 一貫性のコストはどれくらいですか?Aで変更されたものとB&Cで反映されたものとの間の数ミリ秒の不整合を受け入れることはできますか?
  2. ポイントインタイムクエリ(時間クエリとも呼ばれる)が必要ですか?
  3. データの真実の源はありますか?それらを所有し、上流と見なされるサービス?
  4. 所有者/真実の単一のソースがある場合、それは安定していますか?または、頻繁に重大な変更が行われることを期待していますか?

不整合のコストが高い場合(基本的に、Aの製品データは、BおよびCにキャッシュされた製品とできるだけ早く整合する必要があります)、youbは利用不可を受け入れて同期リクエストを行う必要があります(Webなど)。 / rest request)B&CからAにデータをフェッチします。注意してください!これはまだトランザクションの一貫性を意味するのではなく、不一致のウィンドウを最小限に抑えるだけです。確実に、すぐに一貫性を保つ必要がある場合は、サービスの境界を変更する必要があります。しかし、私は非常に強く、これが問題になることはありません信じています。経験上、会社が数秒間の不整合を受け入れることができないのは実際には非常にまれであるため、同期リクエストを行う必要すらありません。

ポイントインタイムクエリが必要な場合(質問で気づかなかったため、上記に含まれていなかったため、間違っている可能性があります)、ダウンストリームサービスでこれを維持するコストが非常に高くなっています(複製する必要があります)決定を明確にするすべてのダウンストリームサービスの内部イベントプロジェクションロジック):所有権をAに任せ、Webリクエスト(または類似のもの)でアドホックにクエリし、Aはイベントソースを使用して、知っているすべてのイベントを取得する必要があります当時の状態に投影して返却する時。これはオプション2かもしれないと思います(正しく理解した場合)。しかし、時間的なカップリングは、重複したイベントと投影ロジックの維持コストよりも優れていますが、コストは高くなります。

ある時点が不要で、データの明確な単一の所有者がいない場合(私の最初の回答では、これはあなたの質問に基づいてこれを仮定しました)、非常に合理的なパターンは表現を保持することです。各サービスで個別に製品の。製品のデータを更新する場合、A、B、Cのそれぞれに並列のWebリクエストを行うことでA、B、Cを並列に更新するか、A、B、Cのそれぞれに複数のコマンドを送信するコマンドAPIがあります。ジョブを実行するためのデータのローカルバージョン。古い場合とそうでない場合があります。A、B、Cのデータが異なり、製品の「全体」が3つのデータすべての構成である可能性があるため、これは上記のオプションではありません(オプション3に近いものにすることもできます)。ソース。

真実のソースが安定したコントラクトであるかどうかを知ることは、AとサービスBとCを統合するためにドメイン/内部イベント(またはAのストレージパターンとしてイベントソースに保存するイベント)を使用するために使用できるので便利です。契約が安定している場合は、ドメインイベントを通じて統合できます。ただし、変更が頻繁に発生する場合や、メッセージのコントラクトが十分に大きいためにトランスポートが問題になる場合には、追加の懸念事項があります。

明確な所有者がおり、安定していることが期待される契約がある場合、最良のオプションはオプション1です。注文にはすべての必要な情報が含まれ、BとCはイベントのデータを使用して機能を実行します。

オプション3に従って、契約が頻繁に変更または破綻する可能性がある場合は、複数のバージョンを維持する方がはるかに簡単であるため、実際には、製品データを取得するためのWebリクエストにフォールバックする方が適切なオプションです。したがって、Bは製品のv3で要求を出します。


うん、同意する。製品カタログの変更を追跡する複雑さを追加ProductAddedまたはProductDetailsChanged追加する一方で、イベントが再生され、過去のカタログデータにアクセスする必要がある場合に備えて、データベース間でデータを同期させる必要があります。
eithed

@私はいくつかの仮定を拡張するために答えを更新しました。
Savvas Kleanthous
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.