Open
32

「Monolith to Microservices」読書メモ

p12 3つのモノリス:シングルプロセス(下位区分としてモジュール化されたモノリス)、分散モノリス、サードパーティシステム

p15 モノリスをレガシーと同義と捉える人々がいる、これは問題だ。モノリスは選択肢のひとつであり、マイクロサービス同様、全ての場合に正しいわけではない

p17 凝集性=一緒に変化するコードは一緒にある
p17 他方で、結合度が上がると、一緒に変化するコードは増える

p18 「実装の結合」はしばしば見られるが容易に削減できる
p19 良くあるのはデータベース共有だが、サービス間にAPIを立てれば良い
p20 あるいは内部テーブルとは別に公開用テーブルを作っても良い
p21 いずれにせよどの情報を公開するかはユーザーに聞いて決めるべし。内部実装よりも先に。

p22 一時的な結合:同期的なhttp呼び出し、全てのサービスが同時に起動していないとダメ。解決策としてはキャッシュ、メッセージブローカーによる非同期呼び出しがある
p23 デプロイの結合:シングルプロセスのモノリスでよくある。デプロイにはリスクがあるので、デプロイ対象は減らすべき。フィードバックも速く得られる。
p23 デプロイの分割にはマイクロサービスだけではない。Erlangのようにホットデプロイができるものもある

p24 ドメインの結合:サービスに不要な情報を隠すことで結合を減らせる。どのサービスにもクレジットカード情報が必要だろうか?
p26 idベースで連携してあとでAPIを呼び出すくらいなら最初から情報を渡してしまえばよい
p27 あるいはイベント駆動にすることで依存性を逆転できる

p28 マイクロサービスを考える上で重要となるドメイン駆動開発の考え方をまとめておく
p29 集合(aggregates):マイクロサービスがそのライフサイクルを管理するデータのまとまり、ドメイン上の概念(注文、送状、在庫など)。

p31 境界づけられたコンテキスト(bounded context):サービス間の実装の詳細を隠す
p31 このふたつにより、凝集性が高まり、よく定義されたインターフェースが実現できる

Chapter 2 Planning a Migration

p33 技術的でない部分から始めよう
p33 マイクロサービスはゴールではない、マイクロサービスを実現したら勝ちではない:既存システムで実現できないことを実現するためのものだ
p35 3つの重要な問い

  • マイクロサービスによって何を達成したい?
  • マイクロサービス以外の代替手段を考えたか?
  • マイクロサービスへの移行が機能していると判断する基準は?

p35 なんのためにマイクロサービスを選択するか?
p35 ひとつにはチームの自律性を高めるため
p36 それにより権限移譲され仕事が速く終わる
p36 そのためにはマイクロサービスでなく、モジュール化されたモノリスでもよい
p37 もうひとつは、市場への時間を短縮するため
p37 本番環境にアイデアが投入されるまでのボトルネックを確認しよう:そもそもプロダクトオーナーがボトルネックかも?

p37 また、コスト効率のよいスケールを実現するため
p38 モノリスでも水平または垂直スケールによる代替が効果的
p38 また、堅牢なアプリにするため
p39 とはいえこれもロードバランサーやキューによって代替できる
p40 より信頼できるハードウェアやソフトウェアへの投資でも代替可能

p40 ひとつには、開発者の人数を増やすためにマイクロサービスを選択している。代替手段として、モジュール化されたモノリスがある程度有効。単にマイクロサービスにするだけではなくチーム間の自律性が不可欠。
p41 ひとつには、新しい技術を採用するためにマイクロサービスを選択している。代替手段として、動作環境がJVMであれば新しい言語を採用しやすい。より良い結果を顧客に提供できるし、開発者も新しい技術をマスターできてハッピー。

p42 マイクロサービスが悪手となるシーン
p43 - 不明瞭なドメインの場合:サービス境界を間違えると高コスト。最初からマイクロサービスをやるよりは、既存のコードを分解する方がはるかに楽。
p43 - スタートアップの場合:マーケットフィットを得ていない場合は特に悪い。また、マイクロサービスが増えたときの運用コストも考慮すること。
p44 - 顧客が管理するソフトウェアの場合:顧客に同じ運用スキルを期待してはいけない
p45 - ゴールが明確でない場合:マイクロサービスによって何を得たいのか?

p47 ゆえに、何を達成したいのか、目的を組織のメンバーと共有することが重要

p48 John Kotter博士の組織的変化の8ステップ

  • 危機意識を高める
  • 連帯チームを作る
  • ビジョンと戦略を生み出す
  • 変革のビジョンを伝える
  • 従業員に変革の権限を与える
  • 短期的な勝利を実現する
  • 成果を生かしてさらなる変化を生み出す
  • 新しい方法を文化として根付かせる

p48 危機意識を高める:マイクロサービスはゴールではない。何を達成したいのかを共有しよう

p49 連帯チームを作る:信頼が重要だ。小さく素早い勝利を経験していれば、自然とあなたの大きな考えを指示してくれるだろう。
p49 ビジョンと戦略を生み出す:ビジョンは現実的かつ野心的を両立することが重要。特定の戦略に過度に入れ込んではいけない、サンクコストに注意。

p50 変革のビジョンを伝える:小さくはじめよう。オフィスのトイレにビラを貼るとか。ビジョンの共有には対面のコミュニケーションがより効果的。

p51 従業員に変革の権限を与える:障害を取り除こう。具体的な問題を解決するために技術を利用しよう。完璧なマイクロサービスプラットフォームを探して時間を無駄にしないこと。ペインポイントを探して、学んで、改善のために投資しよう。

p52 短期的な勝利を実現する:全社的に拡大するのは、小さな成功を手に入れてからで良い。失敗はつきものだが、そこから素早く学ぶことが重要。
p52 成果を生かしてさらなる変化を生み出す:小さな成功に甘んじることなく、ふりかえりを行い、変化を拡大しよう。違う部署では違うやり方が必要かもしれない。
p52 新しい方法を文化として根付かせる:成功と、失敗の物語を共有しよう。組織内で継続的に共有することが変化をスケールさせ定着させる。

p53 漸進的な変化の重要性:少しずつマイクロサービスについて学ぶことができる、失敗のスコープも小さくて済む。人間は失敗するので、失敗のサイズを小さくする工夫が必要。良いアイデアは小さくはじめよう。

p54 本番環境がすべて:重要な教訓は本番環境でしか学べないことが多い。トラブルシューティング、トレーシング、レイテンシ、連鎖的な事故など。
p54 ジェフ・ベゾスの言葉:可逆的な決定と不可逆的な決定がある、後者は注意深く決定すべきだが、大半の決定は可逆的であり、個人や小さなチームによって素早く行われるべき。
p55 実際には、可逆的と不可逆的のスペクトラムになっている
p55 マイクロサービスについての決定は可逆的と位置付けられることが多い:ロールバックすればよいので。

p56 実験しやすいところからはじめよう:データベース分割に手を出すのではなく、ホワイトボードで思考実験をしてみよう
p56 DDDを活用してサービス境界を決定する
p58 ドメインモデルは、継続的に更新すべきもの
p58 技術者ではないステークホルダーも読んで、「イベントストーミング」によってドメインモデルをともに作ろう
p58 重要なのはエクササイズによってモデルが定義されるだけでなく、理解が共有されることだ
p62 ソフトウェアのアーキテクチャと組織の構造を揃えておくことが重要
p63 運用をするチームではなく、他の(開発)チームが運用をできるようにすることが重要:自律的なチームが、ソフトウェアのデリバリーサイクルにより責任を持つようになる
p64 Spotifyモデルが有名だが、あれは2012年のスナップショットにすぎない。いまのSpotifyがSpotifyモデルを使っているわけではない。ベストな組織構造はコンテキストによって異なるので、他の組織の答えをコピーするのではなく、問いをコピーすべきだ。
p68 スキルに関するThe Guardian紙の開発者たちの事例:自分でスキルを5段階で評価、メンターと共有、自身の目標を設定すべきであって、全員が全員最高レベルを目指すべきではない
p69 チーム全体のスキルレベルをみるのに良い。匿名化して集計すると、個人がチームをどのように助けられるか理解する助けになる。
p70 短期的には、足りないスキルがあれば外からスキルのある人を連れてくるのがよい

p73 サンクコストにとらわれることなく定期的にプロジェクトの進行をチェックし、必要な場合には方向を変えよう:変化は一度きりのものではない

Chapter 3 Splitting the Monolith

p77 モノリスのコードがMVCなどの技術的なパッケージ名に分けられていると、マイクロサービス化のためのビジネスドメイン毎の分割は困難である
p77 Working Effectively with Legacy Code by Michael Feathers (Prentice Hall, 2004)のseam(接ぎ目):Javaならjar、Rubyならgemと、モジュール化されたモノリスへ分けるところから始める
p78 モジュール化された段階で大体の組織の抱える問題は解決される
p78 ビッグバン・リライトはしない、少しずつ書き換えて顧客に提供していく
p79 ストラングラーパターン:新旧のシステムがはじめは同時に存在し、徐々に置き換わっていく
p80 デプロイとリリースは別概念:本番環境にデプロイしても顧客に使われていないならリリースされていない。本番にデプロイしてリリース前に検証しよう
p80 旧システムは生きている:失敗は必ず起きるのでその場合はロールバックしよう
p83 ストラングラーパターンにHTTPリバースプロキシを活用する
p84 プロキシがなければまずはプロキシのみデプロイする:しばらく様子を見てネットワークホップが増えた影響を評価する。レイテンシに問題があれば調査して解決しよう
p85 新しいサービスをデプロイする:まずは全リクエストに501 Not Implementedを返す、徐々に機能追加して何度もデプロイしていく。これはリリースではない、顧客には使われていない状態のまま。
p85 必要な全機能をデプロイしたら、プロキシでリダイレクトさせる:ここでも失敗したらロールバックすればよい
p86 リリースの際はカナリア方式や並行稼働方式を利用できる
p86 nginxのようなプロキシを利用するのがよい
p87 自作するとパフォーマンス問題などがおきる
p87 リクエストのボデイベースのマッピングが必要で、URIパスによるマッピングが利用できない場合でも、nginxのlua拡張などで実装するのがよい
p90 プロキシを利用してSOAPからgRPCへのプロトコル変更も可能

p91 プロキシにプロトコル変換ロジックを埋め込むと、プロキシが複雑化し、デプロイの独立性を妨げるようになる:共有ミドルウェアは機能開発を遅延させる
p91 むしろプロトコル変換ロジックはサービス内部に持たせるのがよい
p92 セントラルプロキシではなく、サービスメッシュ、ローカルプロキシという考え方で、共有ミドルウェアを避ける
p98 機能をマイクロサービスへ移行する場合、動作が変わらぬよう注意せよ。新規機能やバグ修正は移行が完了するまで待つべし。なぜならロールバックが困難になるから。
p98 UI合成パターン:既存モノリスとマイクロサービス をつなぎ合わせる
p99 ページ単位の合成、ウィジェット単位の合成
p100 サーバーサイドでApacheのEdge-Side Includesなどが利用されきたが、最近ではブラウザやネイティブアプリで行われることが多い
p102 モバイルアプリの場合、Apple App StoreやGoogle Play storeへの事前提出が障害となる
p103 この意味で、モバイルアプリはモノリスである:Web Componentsを利用したマイクロフロントエンドと呼ばれる手法が採用されてきている
p104 抽象化によるブランチパターン
p105 既存機能を抽象化するレイヤを作り、既存機能の呼び出し元からその抽象化レイヤを呼び出し、抽象化レイヤを新規機能で実装し、新規機能の実装へと切り替えを行い、既存実装(と抽象化部分)を削除する

p.112 フォールバックとして既存実装を呼び出す抽象化によるブランチ検証パターンもある
p.113 同時実行パターン:抽象化によるブランチパターンのように既存・新規のどちらかだけを呼び出すのではなく、両方呼び出して結果を比較する
p.116 非機能的な側面も同時に検証すべき:ネットワーク越しの呼び出しになるので、タイムアウトや失敗率も要確認
p.116 スパイの利用:通知マイクロサービスと既存の通知ロジックが同時実行されると、通知が2通飛んでしまうので、これを避ける必要がある
p.117 同時実行をサポートするライブラリとしてScientistがある

p.118 デコレーティング・コラボレーター・パターン
p.119 メソッド呼び出しをキャッチするプロキシを利用して、別の処理を挟む

p.120 変更データキャプチャ・パターン:デコレーティングの手法は、メソッドの呼び出しまたは戻り値に、新しいマイクロサービスを呼び出すのに必要な情報がすべて含まれている場合に利用できる、そうでない場合には変更データをキャプチャする手法が必要となる
p.122 実装方法1:DBのトリガーを利用する。Oracle DatabaseはWeb APIを呼び出したり、Javaコードを実行したりできる。しかし、数が多くなるとシステムの全容把握が困難になるデメリットも。
p.123 実装方法2:トランザクションログをポーリングする。しかし、DBによって実装方法が異なるので利用可能なツールも異なる。制限は色々あるが、これがもっともきちんとしたやり方。
p.124 実装方法3:バッチデータコピー。定期的にデータを取得していく。データに更新日時を含める必要があることも。

p.124 この章のまとめ:ひとつの方法ですべての場面を賄うことはできないので、複数の手法を混ぜて利用していることがほとんど。

Chapter 4 Decomposing the Database

p.125 モノリスからマイクロサービスへ移行するなら、データベースを分割する必要がある:移行中のデータ同期、論理的・物理的スキーマ分割、トランザクションの一貫性、結合、レイテンシなどの問題が生まれる

p.125 共有データベース・パターン:スキーマのどの部分を安全に変更できるか理解しづらい
p.126 ビューを利用することで緩和できるが、これも完全な解決方法ではない
p.126 共有データベースは、我々の目指す高凝集な業務機能の全く反対のものである

p.127 共有データベースをマイクロサービスで使用してよい状況は2つ:静的参照データ(例:通貨コードや郵便番号)、もしくはデータベースをエンドポイントとして公開している場合
p.127 理想的には、新しいサービスは独立したスキーマをもつべき
p.128 ※データベース≒論理的に分離されたスキーマ
p.128 他のチームメンバーに、解決方法がわからなくても解決したい問題について話してみよう:いずれ新しいスキルや経験を得て簡単に思えるかもしれない

p.128 データベース・ビュー・パターン
p.131 ビューは元のテーブルから限定的な情報のみを公開することで、情報隠蔽information hidingを実現している
p.131 ビューがいつ更新されるか問題:古いデータを見ている危険性
p.131 ビューの制限:読み取り専用、元のデータベースと同じエンジンでなくてはいけない=デプロイの結合度を高める

p.132 データベース・ラッピングサービス・パターン
p.132 データベースへの依存関係を、サービスへの依存関係に移行する

p.135 サービスのインターフェースとしてのDBパターン
p.135 読み取り専用に設計されたデータベース、レポート用など
p.136 サービス本体のDBとは別に、外部用DBとの変換用エンジンを設ける:やはり古いデータを見ている危険性がある
p.136 変換処理をバッチ処理にすると、実行に時間がかかったり起動しなかったりして問題になる:変更データをキャプチャする仕組みの方がよい

p.137 データの所有権を移動する
p.137 共有データベースのことばかり考えてきたが、問題のデータがどこにあるべきかを考える必要がある

p.138 集約を公開するモノリス・パターン
p.138 マイクロサービス:状態と振る舞いの組み合わせ
p.139 必要なデータの集約をモノリスが公開するとき、将来的なマイクロサービスのサービス境界に近づいている

p.141 データ所有権の変更パターン
p.141 モノリスにあるデータが新しくできたサービスの支配下にあるべき場合
p.141 データのライフサイクルを管理するのが誰か
p.142 外部キー制約やトランザクション境界などの問題に取り組む必要がある

p.143 データ同期
p.144 モノリスのデータベースと新しいマイクロサービスのデータベースの間でどれだけの一貫性が必要か?
p.144 共有データベースは超短期的な手法としてのみ検討すべき(データ移行の一部として):期間が長くなればそれだけ大きな痛みを伴う

p.145 アプリケーションでのデータ同期パターン
p.146 ステップ1:バッチ処理で裏で同期する
p.147 ステップ2:アプリが旧DBからデータを読み書き、新DBにもデータを書き込み
p.147 ステップ3:アプリが旧DBにもデータを書きこみ、新DBからデータを読み書き
p.148 アプリケーションコードを分割する前にDBを分割するときに有効なパターン

p.149 トレーサー書き込みパターン
p.150 アプリケーションでのデータ同期パターンの変形:新DBではなく新サービスにも書き込む
p.151 書き込むデータの種類を徐々に増やしていく
p.152 旧DBからモノリスが読み書きするデータと、新サービスから読み書きするデータが存在する
p.153 データが旧DBと新サービスの間で一貫性がないタイミングが生じる:ある程度結果整合性を受け入れざるをえない
p.153 単純なSQLクエリなどでデータ同期の結果を確認できるようにするのが重要

p.154 Squareの注文データの事例
p.154 食品デリバリーの注文という概念が複数のワークフローから参照されていた:個々のワークフローの変更時に関連するデータやコードが必要になる->機能提供時にチーム間で競合を引き起こす
p.155 配達とレストランで参照する部分だけ注文処理サービスに切り出す:注文処理サービスのデータはもともとの注文データの部分集合を表す
p.156 イベントドリブンなアーキテクチャであればこの移行は多少容易になる

p.158 データベース分割:物理的分割か、論理的分割か
p.159 物理分割と論理分割は達成するゴールが異なる

  • 論理的分割は情報秘匿や独立した変更を容易にしてくれる(一方でSPOFになる可能性も)
  • 物理的分割はシステムの堅牢性を改善し、スループットやレイテンシを改善するためのリソース競合を排除するのに役立つ

p.160 分割するのはデータベースが先か、コードが先か?
p.160 マイクロサービス化の完了の定義:アプリケーションコードが独自のサービスとして動作し、そのサービスが管理するデータが論理的に独立したデータベースに保管されている状態
p.161 データベースが先でもコードが先でも、それぞれメリットデメリットがある

p.161 データベースを先に分割する場合:モノリスから二つのデータベーススキーマにアクセスする
p.161 問題があれば戻しやすいが、短期的なメリットは薄い

p.162 境界づけられたコンテキストごとのリポジトリ・パターン
p.163 テーブル間の外部キー制約など、データベースレベルでの制約が障害となりうる
p.163 ドメイン境界にそってコードを分割することで、将来的なマイクロサービス間のつなぎ目を理解しやすくなる

p.163 境界づけられたコンテキストごとのデータベース・パターン
p.164 コンテキストごとに独立したスキーマを持たせる:それぞれのコンテキストはjarファイルの形態で、モジュール化されたモノリスの好例。あとで分割できるが、必要ないことも多い。
p.165 既存システムの焼き直しでなはく新規サービスを作る場合にオススメ。新規プロダクトやスタートアップでマイクロサービスを実装するのは好ましくない。なぜならドメインの理解が成熟しておらず境界を見誤るから。特にスタートアップはドメインが変化しやすい。

p.165 コードを先に分割する場合
p.166 コードを先に分割すると、どのデータが新しいサービスで必要か理解しやすくなる。デプロイも独立してできるので良い。

p.166 データアクセス層としてのモノリス・パターン
p.166 モノリスにAPIを追加する:モノリスは死んで無用の長物というわけではない
p.167 マイクロサービスのデータの基本:状態と、その状態の繊維を管理するコードをカプセル化する
→ モノリスがデータの状態遷移を管理しているならマイクロサービスが状態にアクセスする際モノリスを経由すべき

p.170 データベースとコードを同時に分割する場合:避けた方が良い

p.170 パフォーマンスやデータの一貫性の影響が気になるなら、スキーマを先に分割する:でなければ、コードを先に分割してデータの所有権に関する理解を深める

p.171 テーブル分割パターン
p.171 同一テーブルの別カラムを別サービスが更新している場合:それぞれのテーブルに分割しやすい
p.172 同じカラムを別サービスが更新している場合:状態管理と状態を結合させる原則からいくと、状態管理のための新しいサービスが必要

p.173 外部キー制約をコードに移すパターン
p.174 考えるべき問題は2つ

  • もう片方のテーブルのデータを結合しなければならないときにどうするか?
  • データの一貫性がなくなるのにどのように対処するか?
    p.175 データの結合をコードで置き換える:データを管理するサービスのAPIを呼び出す
    p.176 速度劣化が問題になるなら一括処理やキャッシュで緩和できる

p.176 データの一貫性:別スキーマなのでそのような制約はなくなる
p.176 データを削除する前にチェックする:外部のサービスへの依存が生まれてしまう、データの利用者が増えれば増えるほど問題になる
p.176 グレースフル・デリート:外部サービスが、データがない場合の取り扱いを考慮する。データ削除時のイベントをサブスクライブしてローカルに保存する、なども可。
p.177 削除しない:ソフトデリート(削除フラグ)を実装して、特定機能では該当データを利用しないようにする

p.178 静的データ共有の事例

p.179 静的参照データの重複パターン
p.179 各サービスが国コードのテーブルを持つ
p.180 データが各サービス内でのみ利用されているなら、サービス間でのデータの一貫性は問題にならない:同じデータを参照する必要性があれば問題になる
p.180 データの重複を受け入れることで、サービス間の結合を回避できているとも取れる

p.181 参照専用テーブルパターン
p.181 共有データベースの一種
p.181 SPOFではあるがjoin句を利用できるメリットも

p.182 静的参照データライブラリパターン
p.182 enumなどの形で実装し、jarファイルなどの形で共有する
p.182 利用言語が異なる場合には採用できない
p.183 全サービスで同じデータを更新する必要性がある場合、デプロイの結合が発生してしまう
p.183 各サービスが異なるバージョンを利用することを許容できる場合には最適

p.184 静的参照データサービスパターン
p.184 新しいサービスを簡単に構築できる場合には有効な選択肢
p.184 そうでない場合には「やり過ぎ」に思われがち
p.185 FaaSに最適(AWS Lambdaとか
p.186 データの一貫性が必要ないなら共有ライブラリで十分、必要なら専用のサービスを立てよう

p.187 トランザクション
p.188 データベースを分割してもACID特性を限定的に利用できる

ログインするとコメントできます