Microservices and PaaS Architecture に関する記事を読みました。その記事の約3分の1で、著者は次のように述べています(Crazyのように非正規化します):
データベーススキーマをリファクタリングし、すべてを非正規化して、データの完全な分離とパーティション化を可能にします。つまり、複数のマイクロサービスを提供する基礎となるテーブルを使用しないでください。複数のマイクロサービスにまたがる基礎となるテーブルの共有、およびデータの共有はありません。代わりに、複数のサービスが同じデータにアクセスする必要がある場合、サービスAPI(公開されたRESTまたはメッセージサービスインターフェイスなど)を介して共有する必要があります。
この音は理論的には素晴らしいですが、実際には、克服すべき重大なハードルがいくつかあります。最大のものは、多くの場合、データベースが密結合されており、すべてのテーブルが少なくとも1つの他のテーブルとsome外部キー関係を持っていることです。このため、データベースをnマイクロサービスによって制御されるnサブデータベースに分割することは不可能です。
だから私は尋ねる:完全に関連するテーブルで構成されるデータベースを考えると、これを小さなフラグメント(テーブルのグループ)に非正規化して、フラグメントを個別のマイクロサービスで制御できるようにする方法は?
たとえば、次の(かなり小さいが、例である)データベースがある場合:
[users] table
=============
user_id
user_first_name
user_last_name
user_email
[products] table
================
product_id
product_name
product_description
product_unit_price
[orders] table
==============
order_id
order_datetime
user_id
[products_x_orders] table (for line items in the order)
=======================================================
products_x_orders_id
product_id
order_id
quantity_ordered
デザインを批評するのに時間をかけすぎないでください、私はこれをその場で行いました。ポイントは、私にとって、このデータベースを3つのマイクロサービスに分割することは論理的に意味があるということです。
UserService
-システム内のCRUDdingユーザー用。最終的に[users]
テーブル;そしてProductService
-システム内のCRUDding製品用。最終的に[products]
テーブル;そしてOrderService
-システム内のCRUDding注文用。最終的に[orders]
および[products_x_orders]
テーブルただし、これらのテーブルはすべて相互に外部キー関係を持っています。それらを非正規化し、モノリスとして扱うと、それらはすべての意味的な意味を失います:
[users] table
=============
user_id
user_first_name
user_last_name
user_email
[products] table
================
product_id
product_name
product_description
product_unit_price
[orders] table
==============
order_id
order_datetime
[products_x_orders] table (for line items in the order)
=======================================================
products_x_orders_id
quantity_ordered
今、誰が何を、どの数量で、いつ注文したかを知る方法はありません。
したがって、この記事は典型的なアカデミックなヒラバルーですか、またはこの非正規化アプローチに現実世界の実用性があり、もしそうなら、それはどのように見えますか(答えで私の例を使用するためのボーナスポイント)?
これは主観的なものですが、次のソリューションは私、私のチーム、およびDBチームにとって有効でした。
Contact
サービスは連絡先をCRUDする場合があります(連絡先に関するメタデータ:名前、電話番号、連絡先情報など)User
サービスは、ログイン資格情報、許可ロールなどを持つユーザーをCRUDする場合があります。Payment
サービスは支払いをCRUDし、StripeなどのようなサードパーティのPCI準拠のサービスで内部的に動作する可能性があります。問題はカスケードとサービスの境界にあります。支払いには、誰が支払いを行っているかを知るためにユーザーが必要になる場合があります。このようにサービスをモデリングする代わりに:
interface PaymentService {
PaymentInfo makePayment(User user, Payment payment);
}
次のようにモデル化します。
interface PaymentService {
PaymentInfo makePayment(Long userId, Payment payment);
}
このように、他のマイクロサービスにのみ属するエンティティは、オブジェクト参照ではなくIDによって特定のサービス内で参照です。これにより、DBテーブルはあらゆる場所に外部キーを持つことができますが、アプリレイヤーでは「外部」エンティティ(つまり、他のサービスに存在するエンティティ)がIDを介して利用できます。これにより、オブジェクトのカスケードが制御不能になるのを防ぎ、サービスの境界を明確に示します。
発生する問題は、より多くのネットワーク呼び出しが必要なことです。たとえば、各Payment
エンティティにUser
参照を与えると、1回の呼び出しで特定の支払いのユーザーを取得できます。
User user = paymentService.getUserForPayment(payment);
ただし、ここで提案していることを使用すると、2つの呼び出しが必要になります。
Long userId = paymentService.getPayment(payment).getUserId();
User user = userService.getUserById(userId);
これは取引ブレーカーかもしれません。しかし、スマートでキャッシングを実装し、各呼び出しで50〜100ミリ秒で応答する適切に設計されたマイクロサービスを実装する場合、これらの追加のネットワーク呼び出しをnot応用。
それは確かに、ほとんどの記事で非常に便利に省略されているマイクロサービスの重要な問題の1つです。幸いなことに、これには解決策があります。議論の基礎として、質問で提供した表を用意しましょう。 上の画像は、テーブルがモノリスでどのように見えるかを示しています。結合を持つテーブルはわずかです。
これをマイクロサービスにリファクタリングするには、いくつかの戦略を使用できます。
この戦略では、マイクロサービス間の外部キーが壊れ、マイクロサービスはこのキーを模倣するエンドポイントを公開します。例:製品のマイクロサービスは、findProductById
エンドポイントを公開します。注文マイクロサービスは、参加の代わりにこのエンドポイントを使用できます。
2番目のソリューションでは、2番目のデータベースにテーブルのコピーを作成できます。コピーは読み取り専用です。各マイクロサービスは、読み取り/書き込みテーブルで可変操作を使用できます。他のデータベースからコピーされたテーブルのみを読み取る場合、(明らかに)読み取りのみを使用できます
read only view
ソリューションの上にredis/memcachedなどのソリューションを導入することにより、高性能の読み取りを実現することができます。結合の両側は、読み取り用に最適化されたフラット構造にコピーする必要があります。このストレージからの読み取りに使用できる、まったく新しいステートレスマイクロサービスを導入できます。面倒な作業のように思えますが、リレーショナルデータベース上でのモノリシックソリューションよりも高いパフォーマンスが得られることに注意してください。
可能な解決策はほとんどありません。実装が最も簡単なものは、パフォーマンスが最も低くなります。高性能ソリューションの実装には数週間かかります。
これはおそらく良い答えではなく、一体何だと思います。あなたの質問は:
完全に関連するテーブルで構成されるデータベースを考えると、これをどのように小さなフラグメント(テーブルのグループ)に非正規化するのでしょうか。
WRTデータベース設計 "「外部キーを削除せずにできない」.
つまり、厳密な非共有DBルールを使用してマイクロサービスをプッシュする人々は、データベース設計者に外部キーを放棄するように求めています(そして、暗黙的または明示的にそれを行っています)。 FKの損失を明示的に述べていない場合、外部キーの値を実際に知って認識しているかどうか疑問に思われます(頻繁にまったく言及されていないため)。
テーブルのグループに分割された大きなシステムを見てきました。これらの場合、A)グループ間でFKを許可しないか、B)FKが他のグループのテーブルに参照できる「コア」テーブルを保持する1つの特別なグループがあります。
...しかし、これらのシステムでは、「テーブルのグループ」は多くの場合50を超えるテーブルであるため、マイクロサービスへの厳密な準拠に十分なほど小さくありません。
私にとって、DBを分割するためのマイクロサービスアプローチで考慮する他の関連する問題は、レポートの影響、レポートおよび/またはデータウェアハウスへのロードのためにすべてのデータがどのように集められるかという問題です。
ある程度関連しているのは、メッセージング(およびコアテーブル/ DDD共有カーネルのDBベースのレプリケーション)が設計に与える影響を優先して、組み込みのDBレプリケーション機能を無視する傾向です。
編集:(REST呼び出し)によるJOINのコスト
マイクロサービスの提案に従ってDBを分割し、FKを削除すると、施行された(FKの)宣言的なビジネスルールが失われるだけでなく、それらの境界を越えてDBが結合を実行する機能も失われます。
In OLTP FK値は一般に「UXフレンドリー」ではないため、多くの場合、それらに参加したいと考えています。
この例では、最新の100件の注文を取得する場合、UXに顧客IDの値を表示したくないでしょう。代わりに、名前を取得するために顧客に2回目の電話をかける必要があります。ただし、オーダーラインも必要な場合は、製品IDではなく製品名、SKUなどを表示するために、製品サービスを再度呼び出す必要があります。
一般に、このようにDB設計を分割する場合、多くの「JOIN via REST」呼び出しを行う必要があることがわかります。それで、これを行う相対的な費用はいくらですか?
実際のストーリー:「RESTを介した結合」対DB結合のコストの例
4つのマイクロサービスがあり、それらには多くの「REST経由の参加」が含まれます。これら4つのサービスのベンチマーク負荷は〜15分になります。 (結合を許可する)共有DBに対して4つのモジュールを持つ1つのサービスに変換されたこれらの4つのマイクロサービスは、同じ負荷を〜20秒で実行します。
残念ながら、これはDB結合と「RESTを介した結合」の直接的な比較ではありません。この場合、NoSQL DBからPostgresに変更したためです。
「JOIN via REST」のパフォーマンスが、コストベースのオプティマイザーなどを備えたDBと比較すると、比較的低いパフォーマンスであることは驚きですか?.
このようにDBを分割すると、「コストベースのオプティマイザー」から離れて、独自の結合ロジックを作成するためにクエリ実行計画を実行します(比較的独自に作成します)洗練されていないクエリ実行計画)。
各マイクロサービスはオブジェクトとして表示され、他のORMと同様に、それらのオブジェクトを使用してデータをプルし、コード内で結合を作成し、コレクションをクエリします。マイクロサービスも同様に処理する必要があります。ここでの違いは、各マイクロサービスが完全なオブジェクトツリーよりも一度に1つのオブジェクトを表すことです。 APIレイヤーはこれらのサービスを使用し、データを表示または保存する方法でモデル化する必要があります。
各トランザクションごとにサービスに複数のコールバックを行っても、各サービスは個別のコンテナで実行され、これらのすべてのコールは並行して実行できるため、影響はありません。
@ ccit-spence、交差点サービスのアプローチが好きでしたが、他のサービスでどのように設計して消費できるのでしょうか?他のサービスに一種の依存関係が生まれると思います。
コメントをお願いします