1. ホーム
  2. database

[解決済み] マイクロサービスパターンでデータ非正規化はどのように機能するか?

2023-06-09 22:54:40

質問

私はちょうど次の記事を読みました。 マイクロサービスとPaaSのアーキテクチャ . その記事の中で、3分の1くらいのところで、著者は次のように述べています (以下 狂ったように非正規化する ):

データベーススキーマをリファクタリングし、すべてを非正規化し、データの完全な分離とパーティショニングを可能にします。つまり、複数のマイクロサービスに対応する基礎となるテーブルを使用しないことです。複数のマイクロサービスにまたがる基礎となるテーブルを共有してはいけませんし、データの共有もしてはいけません。その代わり、複数のサービスが同じデータにアクセスする必要がある場合、サービスAPI(公開されたRESTやメッセージサービスインターフェースなど)を介して共有する必要があります。

一方、この が聞こえる 理論的には素晴らしいことですが、現実的には克服すべき重大なハードルがあります。その最大のものは、多くの場合、データベースは密結合で、すべてのテーブルが いくつかの という外部キー関係を持っていることです。このため、データベースの分割が不可能になることがあります。 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つのマイクロサービスに分割することが論理的に理にかなっているということです。

  1. UserService - システム内でユーザを CRUD するためのもので、最終的には [users] テーブルを管理します。
  2. ProductService - を管理する必要があります。 [products] テーブルを管理します。
  3. OrderService - を管理する必要があります。 [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チームにとってうまくいったものです。

  • アプリケーション層では、Microservicesはセマンティック機能に分解されます。
    • 例えば Contact サービスは連絡先 (連絡先に関するメタデータ: 名前、電話番号、連絡先情報など) を CRUD するかもしれません。
    • 例えば User サービスは、ログイン資格、認可ロールなどを持つユーザーを CRUD するかもしれません。
    • 例えば Payment サービスは、決済を CRUD して、Stripe などのサードパーティの PCI 準拠のサービスと連携するかもしれません。
  • DBレイヤーでは、開発者/DB/Devopsの人々が望むようにテーブルを編成することができます。

問題は、カスケード接続とサービス境界です。支払いは、誰が支払いを行っているのかを知るためにユーザーを必要とするかもしれません。このようにサービスをモデル化するのではなく

interface PaymentService {
    PaymentInfo makePayment(User user, Payment payment);
}

このようにモデル化します。

interface PaymentService {
    PaymentInfo makePayment(Long userId, Payment payment);
}

こうすることで、他のマイクロサービスのみに属するエンティティは を参照します。 オブジェクトの参照ではなく、IDによって特定のサービス内部で参照されます。これにより、DBテーブルは至る所で外部キーを持つことができますが、アプリレイヤーではquot;foreign"エンティティ(つまり、他のサービスに住むエンティティ)はIDを介して利用可能です。これにより、オブジェクトのカスケードが制御不能になるのを防ぎ、サービスの境界をきれいに区切ることができます。

それがもたらす問題は、より多くのネットワークコールを必要とすることです。たとえば、もし私が各 Payment エンティティ a User を参照することで、一回の呼び出しで特定の支払いに対するユーザーを取得することができました。

User user = paymentService.getUserForPayment(payment);

しかし、ここで提案するものを使うと、2つの呼び出しが必要になります。

Long userId = paymentService.getPayment(payment).getUserId();
User user = userService.getUserById(userId);

これは破談になるかもしれません。しかし、もしあなたが賢く、キャッシュを実装し、各呼び出しが50~100ミリ秒で応答するようにうまく設計されたマイクロサービスを実装すれば、これらの余分なネットワーク呼び出しは、間違いなく ではなく アプリケーションにレイテンシーを発生させないように細工できることは間違いありません。