ひらめの日常

日常のメモをつらつらと

Cloud Pub/Sub で意識すること

はじめに

最近、仕事で Google Cloud Pub/Sub を使用したシステムの構築に関わる機会がありました。この過程で、Pub/Sub のメッセージ配信の挙動や各種設定について学ぶことができました。

公式ドキュメントが最も信頼できる情報源ですが、その内容を自分なりにメモとしてまとめて残しておくことを目的としています。

送信回数の制御と Exactly-once delivery

Pub/Sub はデフォルトではメッセージの送信は at least once です。つまり、同じメッセージを重複して送信する可能性があります。したがって、デフォルトではSubscriber側では冪等性を保った処理を実装する必要があります。

By default, Pub/Sub offers at-least-once delivery with no ordering guarantees on all subscription types.

ref: Subscription overview  |  Cloud Pub/Sub Documentation  |  Google Cloud

メッセージの重複を排除し、同時に必ず一通のメッセージのみ送信したい場合は Exactly-once delivery という機能を利用できます。これは、一意のメッセージIDによって、ack前に重複したメッセージが送信されることを防ぎます。

Pub/Sub supports exactly-once delivery, within a cloud region, based on a Pub/Sub-defined unique message ID.

ref: Exactly-once delivery  |  Cloud Pub/Sub Documentation  |  Google Cloud

ただし、この機能はメッセージが「重複して」送られないようにするだけであり、「再送」は依然として行われます。英語ではそれを redelivery (再送) and duplicate (重複) という言葉で使い分けています

メッセージ順序と Message Ordering

Pub/Sub はデフォルトではメッセージの順序を保証しません。これは必ずしもPublishした順にSubscriberがメッセージを受け取るとは限らないことを示します。

メッセージの順序を保証したい際は、Message Ordering という機能があります。

Message ordering is a feature in Pub/Sub that lets you receive messages in your subscriber clients in the order that they were published by the publisher clients.

cloud.google.com

Message Orderingを有効化するには、トピックでこの機能を設定し、メッセージにユニークなキーを割り当てる必要があります。このキーは ordering key と呼ばれ、Pub/Subにメッセージを同じ順序で処理させたいグループを識別するために使用されます。メッセージが同一のキーで送信されると、Pub/Subはそのキーに基づいてメッセージを順番に配信します。

この機能の利用時にはいくつか注意点があります。 まず、メッセージの再送ロジックには注意が必要です。Cloud Pub/Sub で Message Ordering が有効化されているとき、 あるメッセージが再送されると、同じ ordering key が指定されている後続全てのメッセージが再送されます。 例えば、同じ ordering key が指定されているメッセージ 1, 2, 3 を順に Publish し、そのうちメッセージ2 がnackされて再送された場合、後続のメッセージ3は例えackされていても再送されます。再送時のアプリケーション側のパフォーマンスについては注意が必要です。
ref: Order messages  |  Cloud Pub/Sub Documentation  |  Google Cloud

また、Subscriber Client が複数存在する際の挙動についても留意する必要があります。「Subscriber Client が複数存在する」とは、一つの Subscription に対して(例えばその Subscriber が水平方向にscaleして)Subscriber が複数存在する状態のことを指します。この時、Pub/Sub は ordering key に基づいて、同じ key のメッセージを同じ Subscriber に送信します。なので一般的な sharding を考える時同様、ordering key の粒度を細かくし、hot spot を避けることが大事です。
ref: Cloud PubSubのOrdering Keyで考慮すること - Carpe Diem

Pub/Sub の Message Ordering については公式ドキュメントの他にこちらの記事も非常に参考になるので是非読んで見てください。
Google Cloud Pub/Sub Ordered Delivery | by Kamal Aboul-Hosn | Google Cloud - Community | Medium

エラーハンドリングと Dead-letter topic

Pub/Sub はデフォルトでは無限回メッセージの再送を行います。なので、retryによって成功し得ないエラーが発生した場合、メッセージが再送され続け、Subscriberが受け取るメッセージ量も常に増加し続けます。全てのメッセージを確実に処理する必要があるアプリケーションでは、エラーを解決しない限り再送され続けるべきなので、この設定でも問題はありません。

しかし、一定のエラーを許容できるシステムの場合は、数回のretryで解決しない場合はそのメッセージの処理を諦めたい場合があります。

その時に使えるのが、Dead-letter topic です。Dead-letter topic を設定すると、ある一定の回数送信に失敗した場合にそのメッセージを Dead-letter topic へと移すことができます。

If the Pub/Sub service attempts to deliver a message but the subscriber can't acknowledge it, Pub/Sub can forward the undeliverable message to a dead-letter topic

ref: Handle message failures  |  Cloud Pub/Sub Documentation  |  Google Cloud

Dead-letter topic に送信されたメッセージやその数は取得可能なので、後日リトライするためのロジックを実装したり、モニタリングして必要に応じてアラートを発生させたりできます。

まとめ

今回は利用する言語によらないPub/Subの注意点を記載しました。

次はGo言語のPub/Subクライアントライブラリを利用する際に意識することについても書ければと思います。よろしくお願いします。

Complicated Subsystem team を立ち上げた振り返り

はじめに

企業のコアロジックを管理し、発展させることは非常に重要な役割を果たします。しかし、企業が成長し複雑性が増すにつれて、技術的負債の増加やチームメンバーの認知負荷の高まりは避けられない課題となります。

本ブログでは、特に技術的な深みと複雑さを持つサブシステムに焦点を当てて、自分が Complicated Subsystem team を立ち上げた際のノウハウと、その試行錯誤の過程について紹介します。

チームトポロジーの説明とその必要性

チームトポロジーは、効率的なソフトウェア開発と運用を支援するために、チームの構造とコミュニケーションのパターンを設計するアプローチです。この概念は、チームが直面する複雑性を管理し、技術的負債の増加を防ぎ、プロジェクトの認知負荷を軽減するのに役立ちます。

チームトポロジーに関する深い理解には、「チームトポロジー」が非常に有益です。また、メルカリのエンジニアリングチームがチームトポロジーをどのように適用しているかについては、メルカリエンジニアリングブログで詳しく紹介されています。

engineering.mercari.com

会社のコアロジック部分に関わる技術的負債と高い認知負荷を抱える課題に直面したため、専門的な知識を必要とするチームである Compliated Subsytem team をチームトポロジーフレームワークに則って適用することで、これらの課題に効果的に対処することが期待されていました。

チームトポロジーに則れば、Complicated Subsystem team の定義は次のようになります

コンプリケイテッド・サブシステムチームは、システムの中でスペシャリストの知識が必要となるパーツを開発、保守する責任を持つ。(中略)このチームの目的は、複雑なサブシステムを含んだり利用するシステムの担当となるストリームアラインドチームの認知負荷を減らすことにある。

Complicated Subsystem team立ち上げの試行錯誤

Complicated Subsystem teamの立ち上げは、Stream Aligned team(主要ビジネスラインに沿って組織されたチーム)との密接なコラボレーションとコミュニケーションから始まりました。当初から、チームを綺麗に分割し、それぞれの領域を分業することはできませんでした。その代わりに、各チームのステークスホルダーが連携し、コアロジックの理解を共有することやタスクの担当範囲を明確化することに重点を置きました。

システムがパッケージやコードレベルで完全には分離されていなかったため、コミュニケーションコストを惜しまずにかけることが重要であると考えました。もちろんシステムとしての分離も進めつつ、このアプローチによってチーム間での認識の齟齬を避け、よりスムーズに業務を進めることができるようになりました。

理想と現実の乖離とその対応

Complicated Subsystem team の立ち上げにおいて、理想と現実の間には大きなギャップが存在しました。最初の挑戦は、理想的なサブシステムの構想と、既存のシステムの泥団子状態との間の乖離をどう埋めるかでした。ここではドメイン駆動設計(DDD)の手法から力を借りました。プロセスは次のような手順で進めました

  1. それぞれのチームが担当するドメインをコンテキストマップに落とし込む (コンテキストマッピングによる戦略的ドメイン駆動設計 - InfoQ)
  2. コンテキストマップとそれに対応するシステムの現状を理解する
  3. 理想的なコンテキストマップと現状のシステムの乖離をリファクタリングで段階的に埋めていく

このアプローチは、サブシステム間の境界を明確にし、各チームの焦点を合わせる助けとなりました。

チーム運用の課題と解決策

運用の進行中には、サブシステムとして振る舞いながらも、コアドメインとしての認識を維持し、価値を主張することの重要性が浮き彫りになりました。チームがサブシステムとしての役割に留まらず、企業全体の戦略的な資産としての位置を確立するためには、プロジェクト優先度の管理と主張が欠かせません。

Stream Aligned team のプロジェクトマネージャー と1 on 1 を定期的に実施してサブシステム側の開発アイテムについて相談を行ったり、横断的なプロジェクト優先度相談ミーティングに参加してサブシステム側から開発アイテムの提案などを行うなどしました。

こういった地道な営みが開発アイテムが決定されづらい状況を克服し、チーム内外でのコラボレーションを改善していたと信じたいです。

まとめ

Complicated Subsystem teamの立ち上げと運用を通じて、チームトポロジーの適用の重要性、初期段階でのコミュニケーション戦略、そしてコアドメインとしての位置付けと価値の主張に力を入れることが、複雑なシステムの管理において非常に重要であることがわかりました。この記事が、同様の課題に取り組む方々にとって参考になれば幸いです!

Go言語のChannelとメモリ管理

はじめに

この記事は、次の素晴らしい動画と参考書を元にして、自分の中でまとめ直したものになります。 まずstack領域とheap領域はどのようなものであって、Go言語ではどのように管理されているかをまとめます。それをもとに、Go言語の channel が並行処理を可能にしている仕組みと、各メモリ領域との関係性について言及しています。(動画では buffered channel について説明されていたので、この記事でもそちらについて説明しています。)

ぜひ元動画と書籍にも目を通してみてください。

stack領域とheap領域

stack

デフォルトのメモリで、goroutine ごとに存在しています。goroutine のローカル変数を全て格納しておく LIFO のデータ構造です。Goで関数が呼び出されると、その関数だけがアクセスできるメモリ空間として stack frame が、確保された stack の中に作成されます。

ある関数が return して終了すると、再利用のために開放されます。関数の実行が終わると、その関数に割り当てられた stack frame は、後続の関数呼び出しで使用できるようになります。

まだ読んでないのですが、stack frame の挙動に関してはこの辺を読めば理解できそうです。 https://go.dev/src/runtime/stack.go

heap

全ての goroutine が共有するメモリのプールのようなもので、動的に割り当てられます。stack 上に存在できない変数は、heap に escape されます。「関数が return した後にその変数が参照されない」ということをコンパイラが証明できない場合、その変数は heap 上に割り当てられます。

heap は Garbage Collector (GC) によって解放される必要があります。heap への割り当てが増えれば増えるほど、GC に負担がかかり、アプリケーション全体としての CPU 使用率が上昇します。

何が heap に割り当てられるか

  • 関数が return した後に参照される 可能性のある (つまり、ないと証明できない)値。例えば関数内で生成した pointer など
  • コンパイラによって、stack に保存しておくにはサイズが大きすぎると判断された変数
  • コンパイラコンパイル時にサイズを判断できない場合。例えば interface や slice, string などは無限に大きくなる可能性があるため、heap 領域に確保される。

channel

channel が並行処理を可能にしている理由と heap

さて、ここで少し話題を変えて channel について触れていきたいと思います。channel を初期化する際、make(chan int, 1) で初期化すると、channel は pointer として初期化され、 heap 領域に確保されます。さらに、channel の内部には lock するために mutex を持っています。この二つの実装により、channel は複数の goroutine からのアクセスを可能にしています。

type hchan struct {
    qcount   uint           // total data in the queue
    dataqsiz uint           // size of the circular queue
    buf      unsafe.Pointer // points to an array of dataqsiz elements
    elemsize uint16
    closed   uint32
    elemtype *_type // element type
    sendx    uint   // send index
    recvx    uint   // receive index
    recvq    waitq  // list of recv waiters
    sendq    waitq  // list of send waiters

    // lock protects all fields in hchan, as well as several
    // fields in sudogs blocked on this channel.
    //
    // Do not change another G's status while holding this lock
    // (in particular, do not ready a G), as this can deadlock
    // with stack shrinking.
    lock mutex
}

ref: go/src/runtime/chan.go at master · golang/go · GitHub

定義を見てわかるように、channel は内部にデータを保持するために buffer (queue) を持っています。ch <- data のように channel にデータを送信すると、内部的にはこの buffer に対して enqueue することになります。興味深いのが、実際に enqueue されるデータは渡したデータそのものではなく、データのコピーであるという点です。

// send processes a send operation on an empty channel c.
// The value ep sent by the sender is copied to the receiver sg.
// The receiver is then woken up to go on its merry way.
// Channel c must be empty and locked.  send unlocks c with unlockf.
// sg must already be dequeued from c.
// ep must be non-nil and point to the heap or the caller's stack.
func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int)

ref: go/src/runtime/chan.go at master · golang/go · GitHub

よって、受け取る側 variable <- ch はコピーされたデータを受け取るため、thread safe にデータをやりとりできます。

また、channel は内部に recvq, sendq という waitq を持っています。これは sudog と呼ばれる構造体を管理していて、channel を介してメッセージのやり取りをしている goroutine を保持しています。この情報を参照することによって、channel に送ったメッセージを適切な goroutine へと送信できているわけです。

channel のメッセージ送信と stack

goroutine 1 が channel に data を送信し、goroutine 2 が stack 上の変数 t に channel からの値を読み込もうとしている時のことを考えます。

この時、goroutine 1 が channel を通して goroutine 2 に対して、値を送ったことを通知し、goroutine 2 を進めてもらうこともできます。Go言語ではそこを最適化していて、goroutine 1 から、goroutine 2 の stack 上にある変数 t に対して直接値を書きみます。 stack は goroutine ごとに生成・管理されるものですが、この場合のみ、他の goroutine から操作されるようです。

終わりに

stackとheapの動作、そしてchannelを介したgoroutine間の通信の仕組みが少しだけ理解できた気がします。特にstack領域に他の goroutine が書き込めることには驚きました。

channel によってブロックされた goroutine がどのように管理されているかについては、自分が以前書いたブログも参考にしてみてください。

hiramekun.hatenablog.com

2023年買ってよかったもの

2023年に買ってよかったものをまとめておくよ

トラックボール

最近しばらくMacトラックパッドを使っていたので半信半疑だったが、買ってよかった。

よかった点

  • 手首が安定するので、トラックパッドを使っている時より指や手首に負担が少ない
  • 親指でのカーソル操作は意外と快適で疲れない
  • マルチペアリングができ、ボタンひとつで接続先を変更できる。私用PCと会社用PCで切り替えが簡単にできる

イマイチな点

  • Miroで細かい作業をするときはトラックパッドほど融通が効かず、少し不便さを感じる時もあった
  • トラックボールの隙間に埃やゴミが溜まるため、定期的に掃除が必要

充電式なので電源がどれだけ持つか不安だったが、体感1ヶ月に1回程度しか充電していない。計測したわけではないが、充電が頻繁に切れて困ったという印象は全くない。

一度安めのモデルを買った後にこちらに買い替えたので、興味がある人は最初からこっちを買った方が良いと思う。こちらをオススメする理由としては、この辺

  • 本体を傾けることができるので、手首への負担を軽減することができる
  • 高いモデルの方はマルチペアリングできる
  • 安めのモデルは電池なので、電池切れした時に結構困る。電池がないと買いに行かないといけない...
  • (結局より良いものが欲しくなる...!)

定価16,000円と少し高めだけど、Amazon Prime Day で買うと11,000円くらいで買えるらしい。買って損はないのでおすすめ。

リュック

出社する機会も増えてきたので、13インチのPCが入り、普段使いもできるリュックを探していた。

このリュックはちょうど13~14インチのPCを入れることができ、その割には大きすぎず、仕事使いも普段使いもできる素晴らしいリュックだと思う。公式ストアが画像豊富なのでぜひ確認してみてほしい。

よかった点

  • デザインが落ち着いていてかっこいい(これ大事)
  • 耐久性の高い素材を使っていて、長く使用できそう。ナイロンなので雨にも強い
  • 中にポケットがたくさんあるので、小物やノートなど必要に応じて収納することができる

イマイチな点

  • 強いていうなら価格が高いが、それだけの価値があると思う

カジュアル目キレイ目どんな服装にも合わせられるし、機能性も申し分ないリュックなのでオススメ

スチーム式加湿器

加湿器は以前から持っていたが、超音波式加湿器だった。買った際は加熱方式をよく調べずに買ったのだが、超音波式加湿器はいくつかデメリットがあることを知った。特に

  • 掃除が大変かつ汚れやすい。放置すると赤カビとかできてしまう...
  • 加湿器内の汚れがそのまま放出されてしまう。場合によっては加湿器病の症状が出ることもある

といった衛生面でのデメリットは大きいものだった。実際加湿器をつけた後に微熱が出たことがあり、それ以降起動することが少なくなってしまっていた。

調べた結果、どうやらスチーム式加湿器は完全に水を蒸気に熱して殺菌された後に放出するので衛生面で優れているということだった。

そこで購入したのが、この象印のスチーム式加湿器だ。1LDKのリビングに置いて、他の部屋も加湿したかったため大きいサイズのものを購入した。

よかった点

  • お手入れが簡単。大きなポットのような構造をしているため、中の掃除が非常にしやすい。パーツも少ないため、お手入れ工数が低く抑えられる。ぜひ公式サイトも見てほしい
  • 大容量なので長時間加湿することができる。サイトに買いてあるように、実際8時間程度は加湿しているように思う。寝ている間ずっと使用できるのが嬉しい

イマイチな点

  • ずっと使用しているとミネラル分がそこに溜まってくる。無害ではあるが、加熱効率が下がったりするので、月に一回程度は重曹クエン酸で掃除が必要
  • 水を加熱する際に少し湯沸かし音がする

実際寝室まで加湿が行き届いていて、寝起きの喉の痛みが激減し、皮膚の調子も良くなった。超音波式と比べると高価だが、購入してよかったと思う。

炊飯器

炊飯器も今年買い替えたものの一つ。先人たちが炊飯器はいいものを買えと言っていた理由がよくわかった。

一人暮らしを始めた際に買った安い炊飯器を長い間使っていたが、イマイチお米が美味しく感じなかった。

買い替えを検討していたところ、どうやら炊飯器にも加熱方式の違いがあるということを知った。今まで使っていた炊飯器はマイコン炊飯器といって底からのみ加熱するため、炊きムラができやすかったり表面が硬くなったりするものらしい。

どうせなら良いものをと思って圧力IH式のこちらの炊飯器を購入したのだが、毎日お米を炊くのが楽しくなった。

よかった点

  • ご飯が美味しい。お米がもちもちしていて甘く感じる
  • 自動洗浄モードがついていて、そこについている汚れは簡単に落としてくれる
  • 他の製品と比べて洗う必要のある部品が少ないのでお手入れが楽

イマイチな点

  • 少し内なべが重いので、洗うときには注意が必要。でもそこまで大きな問題ではない

本当にお米が美味しくて、買った当初はおかずなしでお米だけすごい勢いで食べていた。それほど毎日の食が変わるので、オススメ。

Go言語におけるgoroutineのスケジュールについて

はじめに

実は9月に転職しまして、新しくGo言語を使用しています。個人的に goroutine が少し理解しづらく、自分の中で腑に落ちなかったため、スケジューリングについて調べてみました。

内容の多くは GopherCon2018 の素敵な動画と、書籍『Go言語100Tips』をもとに自分の言葉でまとめ直したものになっています。ぜひこちらもご覧ください。

www.youtube.com

goroutine とは

goroutine とは Go の runtime によって管理される軽量なアプリケーションスレッド です。プログラムによって生成された goroutine は、 Go の runtime scheduler によって自動的にカーネルスレッドに割り当てられます。カーネルスレッドと比べて軽量であり、作成コストやコンテキストスイッチングを低く抑えることができます。

以下は Effective Go より引用

A goroutine has a simple model: it is a function executing concurrently with other goroutines in the same address space. It is lightweight, costing little more than the allocation of stack space. And the stacks start small, so they are cheap, and grow by allocating (and freeing) heap storage as required.

Effective Go - The Go Programming Language

goroutine のスケジューリングの特徴

タスクとスケジューラ

goroutineは、独立したタスクとしてプログラムによって生成されます。これらのgoroutineを適切に管理し、実行する責任を持つのがGoランタイムのスケジューラです。スケジューラの目的は、利用可能なリソース(CPUコアやスレッド)を最大限に活用し、全てのgoroutineが均等に処理されるようにすることです。

つまり、なるべく少量のカーネルスレッドを使用して、以下を達成することを目指します。

  • 高い並行性 (concurrency) を実現する
  • CPU core 数(もしくはそれ以上)の並列性 (parallelism) を実現する

M:N スケジューリング

Goランタイムでは、M:N スケジューリングモデルが採用されています。これは、M個のgoroutineをN個のカーネルスレッドにマッピングすることを意味し、1つのカーネルスレッドが複数のgoroutineを切り替えながら実行することができます。このモデルは、リソースの利用効率を高め、スレッドのオーバーヘッドを最小限に抑えることを可能にします。

スケジューリングの透過性

Goを使用する開発者は、スケジューリングの詳細を気にする必要がありません。プログラマは単にgoキーワードを使ってgoroutineを起動するだけで、後はランタイムが適切にスケジューリングを行います。これにより、開発者は内部の並行処理の複雑さを自らハンドリングする必要がなくなります。

二種類の Queue とその活用

スケジューリングでは、Global QueueとLocal Queueが存在します。

よくあるモデルとしては、一つの Global Queue を全てのスレッドで共有する方式ですが、この方法だと多くのスレッドから一つの Queue にアクセスする必要があります。データ競合状態を解決するため、一時的なロックを取る必要が出てきます。これは全体のパフォーマンス低下を招くため、Goではスレッド固有の Local Queue で基本的にタスクを管理します。

スケジューラーは目的を達成するために、いくつかの方法で Global Queue と Local Queue の二種類の Queue を活用しています。

Work Stealing

各スレッドは主にLocal Queueを使用してgoroutineを処理し、これが空になると他のLocal Queueからgoroutineを「盗んで」きます。このメカニズムを Work Stealing と呼びます。これにより、全てのCPUコアを効率的に利用しようとします(厳密には、継続単位でstealしているらしいのでもう少し調べたい)。

Handoff

バックグランドで動いているスレッドが、ブロックされているスレッドを見つけると、そのスレッドの Local Queue が持っているタスクを他のスレッドの Local Queue へと委譲します。これを Handoff と呼びます。これにより、長い時間 Queue で待たされているタスクが存在しないようにしています。

Preemption

Global Queueは全てのスレッドが共有するバックアップのようなキューであり、必要に応じてここからgoroutineが割り当てられます。その代表的な例が、Preemption による利用です。Goランタイムは、一定の実行時間が経過したgoroutineを一時停止させることがあります。これにより、他のgoroutineがCPU時間を公平に使用できるようになります。この仕組みを Preemption と呼んでいます。その際に中断されたタスクは Global Queue に割り当てられます(Preemption が起こり得るため、プログラムの厳密な実行時間を予測することは難しいとされています)。

最後に

実は、goroutine は fork-join モデルに従っています。goroutine のスケジューリングは、以前まとめたJVMForkJoinPool における挙動と非常によく似ているものと捉えられそうです。 hiramekun.hatenablog.com

2023年のアクティビティ

2023年のアウトプットと参加イベントをふりかえる

アウトプットしたものや参加したイベントなどをまとめてみました。 (LAPRAS より自動生成)

connpass

PR作成

リポジトリ作成

Blog等

LAPRASポートフォリオはこちらから

『絵で見てわかるマイクロサービスの仕組み』を読んだ

今日は頑張らないでまとめてみます。感想ベースです

動機

マイクロサービスについて勉強をしたいが、開発に従事したことがなく、何もわからない状態だった。まずは周辺知識のスキーマを浅く広く入れたいと思い、イラストが豊富で読みやすそうな本を選んだ

感想

マイクロサービスとその周辺技術(主にクラウドネイティブ)について広く説明されていた。自分が開発する際の脳内の引っ掛かりポイントを作ることができたと思う。

また、マイクロサービスアーキテクチャが必要とされている背景や、経営層に対してどうやってその成果を見せていくか、という話にも触れられている点で貴重な本だと感じた。

以下技術的な内容に関する感想

  • Sagaパターンはもちろん実装できればそれが良いと思うが、アプリケーションのコードを綺麗に保ちながら実装することは簡単ではなさそうに見えた。実際の開発ではどのような実装をしているのか、もしくはどの辺で妥協をしているのか気になった
  • CQRS + イベントソーシングがマイクロサービスと相性が良いのは納得できた
  • データベース共有アーキテクチャはデメリットに注目しがちだが、メリットが存在することは留意しておきたい
  • サービスメッシュについては理解が浅かったので、サービスメッシュとのその具体例について知ることができてよかった

次に読みたい本

より詳細にアーキテクチャパターンを学んでみたい

実際にはどのような実装で実現しているのかを知りたい