BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル スタートアップの視点から見たマイクロサービス

スタートアップの視点から見たマイクロサービス

原文(投稿日:2018/08/19)へのリンク

マイクロサービスへの旅を始めると、何を考えるべきなのかを知って呆然とするかもしれません。特に、小さなチームでは。残念なことに、簡単にあてはまる黄金律は存在しません。旅はすべて異なり、どの組織も別の状況に直面します。この記事では、スタートアップの視点から学んだこととチャレンジ、そして、次にマイクロサービスを導入する時にどのようにするかを共有します。

モノリスからマイクロサービスへの旅はどのように始まったか

ごく最初の頃は、私たちは、どの側面もモノリスで始めました。1つのチームで1つの共同製品に取り組み、1つのテクノロジーの蓄積に基づいて、1つのコードベースとして実装しました。これは、しばらくうまくいきました。

しばらくして、すべてが進化しました。チームは成長し、製品にますます多くの機能を追加しました。コードベースは、さらに大きくなり、ユーザ数は増加しました。すばらしいと思うでしょう? しかし…

物事を終わらせるのに、非常に長い時間がかかりました。ミーティング、議論、そして、何かを決定するのに、以前よりも長い時間が必要でした。責任は、明確に割り当てられていませんでした。誰かが責任を感じるまでには、時間がかかりました。例えば、不具合が発生した時に。プロセスが遅くなり、生産性が悪くなりました。

機能が追加されるほど、製品のユーザビリティはますます複雑になりました。継続的な機能修正により、ユーザビリティとユーザエクスペリエンスは悪くなりました。ユーザの問題をうまく解決する代わりに、ユーザをひどく混乱させていました。

モノリスのソフトウェアアーキテクチャのせいで、システム全体に影響を与えずに、新しい機能を追加するのは難しく、新しい変更のリリースは、非常に複雑でした。たった2、3行のコードを変更した時でさえ、製品全体をリビルトしてリデプロイしなければなりませんでした。そのため、リスクの高いデプロイとなり、めったにすることができず、新しい機能のリリースは遅れがちになりました。

物を分割して、移動させる必要があるのは明らかでした。

3年以上前に、私たちは製品戦略を変更しました。ユーザビリティとユーザエクスペリエンスの改善に集中し、1つの製品であるJUST SOCIALを別々のアプリに分割しました。それぞれのアプリが、特定のユースケースを扱います。ドキュメントの共有、リアルタイムコミュニケーション、タスク管理、記事のコンテンツやコーポレートニュースの共有、そして、プロファイルの管理のために、様々なアプリを提供するアイデアを考えました。

その間に、私たちは1つのチームを複数のより小さなチームに分割し、それぞれのチームを、責任が明確に定義された特定の共同アプリに割り当てました。チーム間で最小の影響しか与えずに、自分たちのペースで、自主的にシステムの様々な部分に取り組めるような自律的なチームを確立したかったのです。

1つの製品を別々のコラボレーションアプリに分割し、1つのチームを複数の小さなチームに分離した後、次の論理的で、妥当なステップは、マイクロサービスを導入することによって、ソフトウェアアーキテクチャにも、自律性と柔軟性を反映することでした。

マイクロサービスを導入する動機は、チーム間の影響を最小限にして、システムの様々な部分に、自分たちのペースで自律的に取り組めるようにすることです。個別にコラボレーションアプリを開発、デプロイ、拡大することにより、私たちは変更を素早くリリースしたいと考えています。

私たちは、最初にマイクロサービスのよい候補を明らかにして、マイクロサービスの旅を始めました。よい候補を明らかにするには、よいサービスをモデリングする重要なコンセプトを検討しなければなりません。重要なコンセプトは、サービスとサービスの中の強いつながりの間の緩い結びつきの原則に従います。サービスの中の強いつながりは、通常は、整合性を保っている関係した振る舞いに影響を受けます。ドメイン駆動設計では、関係した振る舞いは、コンテキスト境界に影響を受けます。コンテキスト境界は、ドメインモデルが存在し、よく定義されたビジネス機能に責任を持つサービスを記述するセマンティックな境界です。

私たちの場合、粗いサービス境界を反映しつつ、ハイレベルなコンテキスト境界として、コラボレーションアプリを使いました。それらのアプリは、あとでより細かいサービスに分割するために、よい開始点を表していました。

私たちは、ドキュメント管理を扱うコラボレーションアプリ、JUST DRIVEのコンテキスト境界から始めました。1つのドキュメントは、1人の作者が作成します。作者データは、プロファイルデータに由来します。プロファイルデータは、まだモノリスとして存在するプロファイル管理のコンテキスト境界によって隔離されます。

私たちは、共存サービスとしてこのアプリを構築しましたが、それはスクラッチから出来上がったものでした。実際には、現在のものとまったく同じではありませんが、その代わりに、新しいUIを導入し、より多くの機能を追加し、データ構造に大きな変更を加えました。新しいサービスのコンテキスト境界は、ビジネスロジックを扱うドメインモデルで構成されています。アプリケーションサービスは、ユースケースを組み合わせ、トランザクションと入力と出力のアダプタ、つまり、RESTエンドポイントと永続化管理のためのアダプタを管理します。新しいサービスは、ドキュメントの状態を排他的に持ち、ドキュメントの読み書きができる唯一のサービスとなっています。

前述したように、1つのドキュメントは、1人の作者が作成します。作者のデータは、モノリスとして管理されるプロファイルデータに由来します。

そこで、新しいサービスとモノリスが、どのように影響し合うのかという疑問が生じます。

ドキュメントを表示する度に、プロファイルサービスから作者データを要求するのを避けるために、私たちは、新しいサービスに関連する作者のローカルコピーを保持しました。データの所有権を害しない限り、データの重複は問題ありません。プロファイルに関係するコンテキスト境界がプロファイルの状態を排他的に所有すればよいのです。

ローカルコピーとオリジナルデータは、時間が経つにつれて、違いがでてくるので、モノリスは、プロファイルが更新された時はいつでも、新しいサービスに通知する必要があります。モノリスは、新しいサービスに申し込んでプロファイルが変更されたらすぐに、ProfileUpdatedEventを発行します。新しいサービスは、このイベントを消費し、それに応じてローカルコピーを更新します。

このイベント駆動サービスの相互作用は、サービス間の分離を増加させ、私たちは、モノリスへ、直接、リモートのコンテキスト間のクエリを実行しなくてもよくなりました。これにより自律性を高め、新しいサービスは、ローカルコピーでしたいことができ、より効果的に結びつけられました。ネートワークを通さずに、ローカルコピーによってローカルで作者データを結びつけることができたのです。

私たちは、スクラッチから共存サービスを始め、データ複製の目的のためにイベント駆動サービスの相互作用を導入しました。

私たちが直面したチャレンジとそれらをどのように扱ったか

スクラッチからの共存サービスは、一般的に優れた分解戦略です。特に、何かから離れたい場合は。例えば、時代遅れのビジネスロジックから、または、技術的な積み重ねから離れたい場合です。しかし、私たちの場合は、最初のサービスを分割した時、その手順は一度で実行するには多すぎました。先ほど述べたように、私たちはスクラッチから共存サービスを構築しただけではありません。新しいUIを導入し、さらに多くの機能を追加し、データ構造に大きな変更を加えました。私たちは最初に重すぎる荷を背負い、ずっと後になって結果を手に入れました。しかし、最初は特に、マイクロサービスの初期の経験と自信を手に入れるために、素早く結果を手に入れることは、とても重要です。

次の候補として、私たちは様々なアプローチに従いました。私たちは、次にチャットアプリのハイレベルなコンテキスト境界に注目し、1歩1歩、既存コードを抽出することによって、徐々に増加するトップダウンの分解戦略に従いました。私たちは、最初に、別のウェブアプリとしてUIを抽出し、抽出したウェブアプリがアクセスできるモノリス側のREST-APIを導入しました。このステップで、ウェブアプリを独立して開発し、デプロイできたので、素早くUIの部分を繰り返すことができました。

UIを抽出した後で、私たちはさらに進んで、ビジネスロジックを分解できました。ビジネスロジックをわかりやすくすることは、大きなコードの変更を生み出します。依存の状態にもよりますが、抽出したビジネスロジックをモノリスが扱うために使う、仮のREST APIを提供する必要があるかもしれません。この段階で、私たちは、まだ同じデータストレージを共有しています。

分離したスタンドアローンのサービスになるために、新しいサービスがチャット状態を排他的に所有するためのデータストレージを最後に分離する必要がありました。

チャットディスカッションのそれぞれのチャットで、参加者が関係しました。チャット参加者データは、モノリスが存在するプロファイルデータから取り除かれます。DRIVEの例で述べたように、私たちは、チャット参加者データのためにローカルコピーを保持し、モノリスからのオリジナルのデータをローカルコピーと同期させるために、ProfileUpdatedEventを購読しました。

この点で、私たちは続けて、モノリスから次のコンテキス境界を切り開き、その後、粗いサービスを細かい物に分解することができました。

もう1つのチャレンジは、認証の扱いでした。

ほとんどすべてのサービスで、私たちは、認証の扱い方に対する疑問に直面しました。その内容を示すと、認証の扱いは、非常に細かく、ドメインオブェクトのレベルまで下がります。コラボレーションアプリは、それぞれドメインオブジェクトの認証を制御します。例えば、ドキュメントの認証は、ドキュメントのある親フォルダの認証設定によって制御されます。

一方、認証は、細かく設定できるだけでなく、内部サービスに依存します。あるケースでは、ドメインオブジェクトの認証は、別のサービスにある親ドメインオブジェクトの認証にも依存します。例えば、コンテンツページに添付されたドキュメントの読み書きは、ドキュメント自体とは異なるサービスにあるページの認証設定に依存します。

これらの複雑な要求のため、分散された認証を解決することは、私たちの頭痛の種でした。そのため、私たちは、早い時期に解決策を提供できませんでした。その結果起きたことは、まったく逆効果でした。1つの結果は、システムのその部分に新しいサービスを追加したことです。その部分では、モノリスに対して、認証はすでに解決していました。私たちは、モノリスを減らす代わりに、モノリスを与えました。もう1つの結果は、サービス毎に認証を実装し始めたことです。ごく初期の頃、私たちには、それが筋の通ったことに思えました。初期の想定では、認証は、ドメインモデルが存在するのと同じコンテキスト境界に属していました。しかし、私たちは、内部サービスの依存を見逃していました。その結果、私たちは、データをあちこちにコピーして、衝突のリスクを持ち込みました。

長い話を短くすると、最終的に、私たちは、認証の扱いを中央に集めたマイクロサービスにマージしました。

中央に集めたサービスと共に、分散モノリスを持ち込むリスクがやってきます。システムのある部分を変更する時に、同時に別の場所も変更しなければなりません。これは、分散モノリスを持ち込んだことを強く示しています。例えば、私たちの場合、認証を必要とする新しいコラボレーションアプリを導入すると同時に、中央に集めた認証サービスを調整する必要がありました。これは、両方の不利な点が組み合わさっています。サービスはしっかりと結合されて、さらに、遅くて信頼できないネットワーク上で通信しなければなりません。

代わりに、私たちは、中央に集めた認証サービスが所有し、すべての下流のサービスが従う共通契約を提供しました。私たちの場合、サービスは、共通契約に関連する認証のアクションを解釈します。認証は、余分な解釈がなくても、この共通契約を理解します。その解釈は、中央に集めた認証サービスではなく、下流のサービスでそれぞれ発生します。この共通契約により、中央に集めたサービスを同時に触って再デプロイすることなく、今では、新しいサービスを導入できます。1つの前提条件は、この共通契約が安定しているか、少なくとも下流に対して互換性があることです。そうでなければ、絶えず更新する必要がある下流のサービスに問題が移動します。

私たちが学んだこと

特に最初は、素早く結果を得て、マイクロサービスの最初の経験をするために、抽出するのが簡単な小さなサービスから始めるとよいでしょう。粗い、大きなサービスを扱う場合は、繰り返しのステップに分解すると、より管理しやすくなります。例えば、繰り返しのトップダウンの分解、つまり、一度に管理できる1ステップをすることです。

初期の段階で、分野横断的な関心事を扱うことは、例えば、モノリスを減らす代わりにモノリスを与えたり、各サービスで分野横断的な関心事を再度実施したりするような逆効果の結論を避けるために不可欠です。

中央に集めた分野横断的なサービスを導入する場合、分散モノリスを導入しないように注意する必要があります。この場合、共通の安定した契約は、分散モノリスを避けるのに役立ちます。

簡単に進化するようにシステムを設計するには、サービス間の分離を高めるためにイベント駆動サービスの相互作用が欠かせません。イベントは、通知のため、そして、データ重複の目的のために使われます。(イベント駆動状態転送; 上述の「スクラッチからの共存サービス」の例をご覧ください。) また、長期間、イベントを保持することによって、イベントストアを通して、プライマリデータソースとしても使われます。

単に通知の目的でイベントを使う場合、他のコンテキストからの追加のデータは、一般的に、リモートのコンテキスト間のクエリによって、そのソースへ要求されます。例えば、REST要求を通して実行されます。私たちは、ローカルでデータセットを維持するオーバヘッドを扱うよりも、特に、データセットが成長している時は、リモートクエリのシンプルさを好むかもしれません。しかし、リモートクエリは、サービス間の結びつきを増やし、ランタイムにサービスを結びつけます。

私たちは、リモートクエリを内在化することで、別のコンテキストへのリモートクエリを避けられます。関連するコンテキスト間のデータのローカルコピーを持つのです。先ほどのJUST DRIVEの例で述べたように、ドキュメントを表示する度に、プロファイルサービスから関連する作者データを要求することを避けるために、私たちは、作者データを複製し、ドキュメントマイクロサービスの中に、ローカルコピーを保持します。複製したデータは、オリジナルのデータと同期させておく必要があります。つまり、オリジナルデータが変更されたらすぐに、ローカルコピーを更新します。修正されたデータの通知を受け取るために、サービスは、変更されたデータを含むイベントを購読し、それに応じて、ローカルコピーを更新します。この場合、イベントは、データ複製の目的で使われ、リモートクエリを削除し、サービス間の分離を増やします。また、これは、よりよい自律性をもたらします。サービスは、ローカルコピーで何でもすることができます。

イベント駆動サービスの相互作用のために、私たちは、分散型の耐故障性のある拡大可能なコミットログサービスであるApache Kafkaを、初めに導入しました。最初、私たちは、Apache Kafkaを主に通知とデータ複製の目的で使いました。最近、私たちは、真実を示す共有のソースとして、Apache Kafka Streamを導入しました。これにより、データ複製のオーバヘッドを除き、サービスの接続をしやすくして、新しいサービスに入るための障害を低くします。

ストリームは、構造化データレコードを、制限なく、規則正しく、連続して更新するシーケンスです。1つのデータレコードは、キーバリューのペアで構成されます。

Apache Kafkaストリーミングコンテキストでサービスを始める場合、Kafkaのトピックは、サービスの範囲内で処理できるストリームにロードされます。トピックは、サービスが発行して購読できる論理的なカテゴリです。各ストリームは、ライトウェイトな組込ディスクでバックアップされたデータベースである、状態トスアのバッファに入ります。ロードされたストリームは、自分のコードベースで使われ、Kafkaブローカ上では実行されません。自分のマイクロサービスのプロセスの中で実行されます。ストリームでは、データが必要な時はいつでも利用でき、パフォーマンスと自律性を向上させます。

Apache Kafkaは、Stream APIと共に利用します。ストリームは、ドメイン特化言語(DSL)を使って、1つになったり、フィルタリングしたり、グループ化したり、集合したりできます。このストリームの各メッセージは、map、transform、peek等のファンクションのようなオペレーションを使って、一度に処理されます。

ストリーム処理を実装する場合、一般的に、効果を高めるために、ストリームとデータベースの両方が必要です。KafkaのStream APIは、ストリームとテーブルの中心となる抽象化を通して、この機能性を提供します。実際に、ストリームとテーブルの間には、密接な関係があります。いわゆる、ストリームとテーブルの二元性です。ストリームは、テーブルの変更ログとして考えられます。ストリームの各データレコードは、テーブルの状態変更を記録します。テーブルは、ある時点における、ストリームにある各キーの最新の値を持つスナップショットとして考えられます。

私たちが、作者データと共にドキュメントを表示したい場合、今ではKafka Streamで次のようにします。ドキュメントサービスは、ドキュメントトピックからKStreamを作成しています。そして、プロファイルトピックの作者に関係するプロファィルデータによって、ドキュメントデータの価値を高めようとします。ここで価値を高めるために、ドキュメントサービスは、プロファイルトピックからKTableを作成します。ストリームとテーブルを1つに結合できるので、その結果を、外部からアクセスできる新しい状態トスアとして保持します。それが、ビルトインのマテリアライズドビューとして動きます。プロファイルやドキュメントが更新された時はいつでも、関係するマテリアライズドビューも更新されます。

他のイベント駆動アプローチと比べて、Apache Kafka Streamは、ローカルコピーのメンテナンスを必要としません。そのため、データ重複のオーバーヘッドを減らし、データを同期させ続けます。必要なところにデータをプッシュし、あなたのサービスとして同じ処理の中で実行します。接続性を向上させるので、新しいサービスを接続し、余分なデータストアを設定せずに、すぐにストリームを使えます。オーバーヘッドを減らし、パフォーマンスと自律性を向上させ、新しいサービスに入る障壁を低くします。

変換プロセスは個別に実行しておらず、むしろ様々な環境によって影響を受けます。チームのサイズや構成、スキルセットは、あなたが管理できることに影響を与えます。特に最初の頃は。例えば、DevOpsを少しだけ実践している小さなチームは、変換のベロシティに影響を与えるでしょう。

変換プロセスは、あなたがまだレガシシステムの面倒を見なければならないという事実にも影響を受けます。このメンテナンスの時間は、変換プロセスに利用できる時間を減らします。ランタイム環境もまた、あなたの旅に影響しています。あなたは、自社運用していますか? それとも、クラウドネイティブですか? 例えば、マネジドAPI-Gatewayのようなマネジドサービスをあてにできますか? それとも、自分自身で、設定して、メンテナンスしなければなりませんか?

そして、あなたの戦略が短期間で新しい機能を導入することであるならば、新しい要求を実装する場所を決めるのに苦労するかもしれません。新しいスタンドアローンサービスは、時間がかかり、近道をしたり、モノリスにそれを追加したりします。そして、モノリスを縮小する代わりに、モノリスを与えるというリスクを犯します。

あなたを引き止め、遅らせる環境に注意し、しかるべく調整を行いましょう。少なくとも、組織で気付くようにしましょう。そして、覚えていてください。すべての旅は異なります。あなたの旅は、私たちのものとはまったく違って見えるかもしれません。

次にマイクロサービスを導入する時に、どのようにするか?

最初に、組織の戦略が、製品ベロシティを最大化するマイクロサービスのゴールに合致しているか、そして、独立して、素早く変更をリリースするかを確認します。例えば、あなたの組織が、長期的なリリースサイクルと、すべて一緒にデプロイすることに注目しているならば、マイクロサービスの利点を完全に生かすことができないので、マイクロサービスは最適な選択ではないかもしれません。

マイクロサービスの旅を続けることを決めたならば、マネジメントを含め、皆がコミットする必要があります。そして、この旅が複雑で時間がかかることを、皆が分かっている必要があります。特に、まだ経験のない最初の頃は。

製品に合わせた機能横断型の自律的なチームは、マイクロサービスで非常にうまくいきますが、DevOps文化にシフトすることを、ごく初期の段階で検討すべきです。各チームは、絶えず続くイテレーションの準備をして、自分たちが責任を持つサービスを、開発、リリース、運用、そして、モニタリングできるべきです。

モノリスを複数の独立したサービスに分解することは、旅の一部分でしかありません。それらを運用するのは別のことです。多くのサービスを持てば持つほど、ビルドとデプロイのプロセスを自動化することは、より不可欠になります。

もし私がもう一度この旅をしなければならないならば、私は抽出するのが簡単な、小さな候補から始めます。そして、分解に注目するだけでなく、ビルドとデプロイの自動化、そして、最初のサービスから前もってモニタリングすることに注目します。そうすれば、将来のサービスの基礎として使うことができます。その基礎を作るために、各チームから1人ずつ集めた、一日限りのタスクフォースを作ることは役立つかもしれません。

マイクロサービスは、最初から、それぞれ自分のCI/CDパイプラインを持つべきです。もう1つ考慮することは、各段階を通して、一貫した、ライトウェイトでカプセル化されたランタイム環境を得るために、マイクロサービスをそれぞれコンテナ化することです。特に、後で、サービスをクラウド環境で実行することに注目しているならば。

また、ログの集合を含むモニタリングは、初期の段階で考慮すべきです。サーバだけでなく、要求待ち時間、スループット、エラー率のようなサービスの測定基準をモニタリングすることは、サービスの健全さと利用可能性を記録しておくために必要です。時間形式(例えば、ISO8601)やタイムゾーン(例えば、UTC)のようなログ出力を構築して標準化すること、そして、相互関係のあるidやログの集合により要求コンテキストを導入することは、診断と科学捜査の処理を容易にします。

多くのことを前もって扱う必要があります。それには時間がかかることを、組織全体で知っている必要があります。マイクロサービスは、製品ベロシティの最大化を成し遂げるための投資であり、費用削減に関することではありません。

市場における競争力、製品ベロシティ、継続的改善を持ち続けることは、競合相手からあなた自身を差別化する重要な要素です。マイクロサービスは、製品ベロシティと継続的改善を促進できますが、それは、マネジメントを含む、全員がコミットした場合だけです。

著者について

Susanne Kaiser氏は、ドイツ、Hamburgの独立した技術コンサルタントで、以前は、モノリスからマイクロサービスへSaaSソリューションを変換する、スタートアップのCTOとして働いていました。コンピュータサイエンスのバックグラウンドを持ち、15年以上のソフトウェア開発とソフトウェアアーキテクチャの経験を持ち、定期的に国際技術カンファレンスで発表しています。

この記事に星をつける

おすすめ度
スタイル

BT