Slide 1

Slide 1 text

© LayerX Inc. コンパウンドプロダクト開発の質とスピードを支える Protobuf と Connect 2024-05-22 アーキテクチャを突き詰める Online Conference @izumin5210

Slide 2

Slide 2 text

© LayerX Inc. 2 ▸ Wantedly, Inc. (2018-04 - 2022-08) ▸ LayerX (2022-09-) ‐ バクラク事業部 Enabling Team ‐ Backend と Web Frontend 中心にやってます ▸ 最近のお気に入りパッケージは @bufbuild/[email protected] 画像を入れてね whoami @izumin5210

Slide 3

Slide 3 text

バクラクのコンパウンドプロダクト開発 このあとの技術の話のモチベーションを伝わりやすくするため、さっくりと…

Slide 4

Slide 4 text

4 © LayerX Inc. 今日の話の前提: バクラクについて LayerX Company Deck https://speakerdeck.com/layerx/company-deck?slide=23

Slide 5

Slide 5 text

5 © LayerX Inc. 今日の話の前提: バクラクについて ビジネスドメインの拡大を実現する バクラクシリーズでのモノレポ開発 (オレンジの傍線は筆者による強調) https://speakerdeck.com/layerx/bakuraku-devsumi-2024-yyoshiki41?slide=13

Slide 6

Slide 6 text

6 © LayerX Inc. 今日の話の前提: バクラクについて ビジネスドメインの拡大を実現する バクラクシリーズでのモノレポ開発 (オレンジの丸は筆者による強調) https://speakerdeck.com/layerx/bakuraku-devsumi-2024-yyoshiki41?slide=14

Slide 7

Slide 7 text

© LayerX Inc. 7 ▸ プロダクト: お客様に1つのパッケージとして価値を提供するソフトウェア製品 ‐ (「バクラク申請」など、右下の図における ○ を指すイメージ) ▸ サービス: 論理的あるいは物理的に分けられた1つのサーバアプリケーション ‐ マイクロサービス・アーキテクチャにおける 「サービス」という単語と概ね同等 発表内での用語の定義 コンパウンドプロダクトの開発の大変ポイント

Slide 8

Slide 8 text

8 © LayerX Inc. 今日の話の前提: バクラクについて 請求書受取・仕訳 申請・経費精算 電子帳簿保存 請求書発行 ビジネスカード Service Service Service Service(組織情報等) Service Service ▸ プロダクトごとに1つ以上のサービス*がある ▸ あらゆるプロダクトから利用される情報は サービスが分かれていたりする * 本発表では「サービス」という単語をマイクロサービス・アーキテクチャにおける 「サービス」と同じような意味で使っていきます

Slide 9

Slide 9 text

9 © LayerX Inc. 今日の話の前提: バクラクについて - 共通のデータを元に統合されたユーザ体験 #とは 請求書受取・仕訳 申請・経費精算 電子帳簿保存 請求書発行 ビジネスカード ▸ カード利用と稟議の紐づけをラクにしたい ▸ カード利用後の報告(領収書等の提出)をラクにしたい ‐ 忘れてたら教えてほしい ▸ ... 「共通のデータを元に統合されたユーザ体験」の例

Slide 10

Slide 10 text

10 © LayerX Inc. 今日の話の前提: バクラクについて Service Service Service(組織情報等) Service Service Service プロダクト間連携を実現するために サービスが増えることもある ※ 細かい構成は今回の話では重要ではないため仮のものです Service Webapp バクラク申請 カードや利用明細を 申請時に見たい カードの明細を取りに行ったりする 非同期の処理や通信もある(e.g. 通知) サービスまたぐ・またがないどちらもある

Slide 11

Slide 11 text

© LayerX Inc. 11 ▸ プロダクトが複数ある 👉 サービスが複数ある ▸ プロダクト間の連携がある 👉 サービス間の通信がある ‐ サービスがうまく通信・連携することがそのまま価値の拡張につながる ‐ 通信・連携もいろいろ ‐ フロントエンド - (自分の|隣の)バックエンド ‐ バックエンド - バックエンド ‐ 同期 ‐ 非同期 コンパウンドプロダクトの開発 コンパウンドプロダクトの開発の大変ポイント

Slide 12

Slide 12 text

© LayerX Inc. 12 「隣のサービスから叩かれるAPIを作る」「隣のサービスのAPIを叩く」 「隣のプロダクトの情報をフロントで見せる」「非同期の処理やAPIを作る」 「(そもそも)複数のサービスをローカルで動かす」 etc. このあたりの手間を減らせないと、開発はどんどん大変になっていく…! 👉 これを Protobuf と Connect の特性を活かして大変さを和らげていく コンパウンドプロダクトの開発 コンパウンドプロダクトの開発の大変ポイント

Slide 13

Slide 13 text

© LayerX Inc. 13 ▸ 今日はあまりに時間がないので、他の資料を参考にしていただけると… 🙏 ‐ コンパウンドスタートアップというLayerXの挑戦 * ‐ ビジネスドメインの拡大を実現する バクラクシリーズでのモノレポ開発 ** ▸ もしくは OpenDoor(カジュアル面談)などで聞いて下さい! おまけ: 結局コンパウンドって何?マルチと何が違う? コンパウンドって何 * https://comemo.nikkei.com/n/n7332c93f50c7 ** https://speakerdeck.com/layerx/bakuraku-devsumi-2024-yyoshiki41

Slide 14

Slide 14 text

Protobuf と Connect なぜ嬉しい?

Slide 15

Slide 15 text

© LayerX Inc. 15 ▸ gRPCと互換性をもつ、 HTTP APIを実装するためのフレームワーク ▸ gRPC Protocolとの完全な互換性に加え、 human-readable and debuggableなConnect Protocolをサポート ‐ JSONやProtobufをHTTP/1.1の上でやりとりできる(片方向通信の場合) Connect #とは Protobuf と Connect https://connectrpc.com/

Slide 16

Slide 16 text

© LayerX Inc. 16 ▸ 強力な型安全性: Protocol Buffers(protobuf)を使用してデータをシリア ライズし、厳密な型チェックと効率的なデータ交換を実現します。 ▸ サービス定義の自動生成: サービスインターフェースとメッセージ定義から自動 的にクライアントとサーバーのコードを生成します。 ▸ (高性能, 複数言語の対応, 双方向通信 も挙げられたが、ここでは割愛) gRPC #とは (gRPCの特徴 by ChatGPT) Protobuf と Connect

Slide 17

Slide 17 text

© LayerX Inc. 17 gRPCのコード生成 Protobuf と Connect message GreetRequest { string name = 1; } こういうmessage定義から… type GreetRequest struct { // private fields Name string `...` } こういう構造体定義が生成される

Slide 18

Slide 18 text

© LayerX Inc. 18 gRPCのコード生成 Protobuf と Connect service GreetService { rpc Greet(GreetRequest) returns (GreetResponse) {} } こういうサービス定義から…

Slide 19

Slide 19 text

© LayerX Inc. 19 gRPCのコード生成 Protobuf と Connect type GreetServiceClient interface { // クライアントがリクエストするとき の interface Greet(context.Context, *connect.Request[v1.GreetRequest]) (*connect.Response[v1.GreetResponse], error) } type GreetServiceHandler interface { // サーバが実装することになる interface Greet(context.Context, *connect.Request[v1.GreetRequest]) (*connect.Response[v1.GreetResponse], error) } こういうのを生成してくれる(これは connect-go の生成物の一部) ボイラープレート的な処理はgRPCやConnectがうまくやってくれる

Slide 20

Slide 20 text

© LayerX Inc. 20 Protobuf + Connect(gRPC) 嬉しいポイント① Protobuf と Connect ▸ 実装がサービス定義に従うことが保証される ▸ Protobuf により互換性が保たれやすい・壊れても Lint で気付ける ‐ 隣のサービスは別のチームの持ち物であるとき、これは特に重要 ▸ ボイラープレートから解き放たれる ‐ サーバ実装ではもう json.Unmarshal とかしなくていいし、型も信用できる クライアントもサービス定義そのままな interface を叩くだけ

Slide 21

Slide 21 text

© LayerX Inc. 21 Protobuf + Connect(gRPC) 嬉しいポイント① 続き Protobuf と Connect ▸ それなりに直感的に書けるIDL(Interface Definition Language) ‐ cf. yaml とかで書くよりは、プログラムを書いてる気持ちで書けるので楽(書く気になる) ▸ Protobuf IDLによるスキーマ定義が「コミュニケーションツール」となり、 「ドメイン理解促進のための議論の場・ドキュメント」になる * ‐ コード生成やIDL自体が複雑なシステムでもスピード(開発速度)を保ち、 かつ質(内部品質)も維持するための武器になる * Protocol Buffers によるプロダクト開発のススメ - API 開発の今昔 https://www.wantedly.com/companies/wantedly/post_articles/309513

Slide 22

Slide 22 text

© LayerX Inc. 22 Protobuf + Connect 嬉しいポイント① Protobuf と Connect 「隣のサービスから叩かれるAPIを作る」「隣のサービスのAPIを叩く」 「隣のプロダクトの情報をフロントで見せる」「非同期の処理やAPIを作る」 「(そもそも)複数のサービスをローカルで動かす」 👉 Connect(gRPC)の基本的な要素で、上で強調している2つは軽減できる!

Slide 23

Slide 23 text

© LayerX Inc. 23 Protobuf と Connect ここまではわりと一般的な Connect(gRPC) の話

Slide 24

Slide 24 text

Connectでローカル開発を楽にする 楽にしたい!

Slide 25

Slide 25 text

© LayerX Inc. 25 ローカル開発はなにが大変か Connectでローカル開発を楽にする gRPCのデバッグ 地味に面倒 サービスいっぱい 起動するの面倒 新しいサービス 作るの面倒

Slide 26

Slide 26 text

© LayerX Inc. 26 新しいサービス作るの面倒問題 Connectでローカル開発を楽にする 新しいサービス 作るの面倒 ▸ 新しいサービスを増やすのはだいたい面倒 ‐ 適切な場所にディレクトリ切って、 エントリポイントのファイルおいて、 必要なミドルウェア差し込んで、、、 ‐ インフラの準備とかもある

Slide 27

Slide 27 text

© LayerX Inc. 27 新しいサービス作るの面倒問題 Connectでローカル開発を楽にする 新しいサービス 作るの面倒 ▸ .protoからサービスのボイラープレートをscaffoldする ‐ 1 gRPC service = 1サービス というのを制約として置く ‐ それだけ決めておけば、Proto Package, gRPC Service から 新しいサービスを立ち上げるための諸々をガバっと生成できる ‐ ちゃんと graceful shutdown したり、 共通のロガーや認証のミドルウェアを入れたり、 …

Slide 28

Slide 28 text

© LayerX Inc. 28 gRPCデバッグ面倒問題 Connectでローカル開発を楽にする gRPCのデバッグ 地味に面倒 ▸ gRPCではcURLなどHTTP APIのデバッグ技法は使えない ‐ grpcurlやEvansなどの便利なツールはあるし、 PostmanやInsomniaはgRPCにも対応している それでも、何かしら専用のツールなり設定は必要になる

Slide 29

Slide 29 text

© LayerX Inc. 29 gRPCデバッグ面倒問題 Connectでローカル開発を楽にする gRPCのデバッグ 地味に面倒 ▸ ConnectではcURLが使える! ‐ JSON over HTTP/1.1でもリクエストを受けられるので、 普段使ってるツールや知識で戦える範囲が広い!

Slide 30

Slide 30 text

© LayerX Inc. 30 サービスいっぱい起動するの面倒問題 Connectでローカル開発を楽にする サービスいっぱい 起動するの面倒 ▸ 手元でプロセスいっぱい立てるのがそもそも面倒だし、 ポート番号の管理もしたくない… ▸ どうする?

Slide 31

Slide 31 text

© LayerX Inc. 31 サービスいっぱい起動するの面倒問題をなんとかする Connectでローカル開発を楽にする curl \ --header "Content-Type: application/json" \ --data '{"name": "izumin5210"}' \ http://localhost:3000/greet.v1.GreetService/Greet ConnectサーバにcURLでリクエストする例

Slide 32

Slide 32 text

© LayerX Inc. 32 サービスいっぱい起動するの面倒問題をなんとかする Connectでローカル開発を楽にする curl \ --header "Content-Type: application/json" \ --data '{"name": "izumin5210"}' \ http://localhost:3000/greet.v1.GreetService/Greet Protobufのパッケージ+ gRPCのサービス名 👉 サービス毎にpathの先頭が異なることが保証できる

Slide 33

Slide 33 text

© LayerX Inc. 33 ローカルでは1つのHTTP serverに 全Connect serverを乗せる Connectでローカル開発を楽にする TenantService NotificationService CardTransactionService 論理的には別のサービス ローカルでは物理的には1つのサービス TenantService NotificationService CardTransactionService 社内ではgo-allと呼ばれています

Slide 34

Slide 34 text

© LayerX Inc. 34 ローカルでは1つのHTTP serverに 全Connect serverを乗せる Connectでローカル開発を楽にする TenantService NotificationService CardTransactionService ▸ ローカルでは全サービスを1つに まとめることで、起動を楽にしている ▸ cURLで雑デバッグしたいときも ポート番号を1つだけ覚えておけばいい ▸ (論理的なサービス単位と物理的なサービス単位を 分けて考える話は本番環境でも使える)

Slide 35

Slide 35 text

© LayerX Inc. 35 Protobuf + Connect 活用ポイント② Protobuf と Connect 「隣のサービスから叩かれるAPIを作る」「隣のサービスのAPIを叩く」 「隣のプロダクトの情報をフロントで見せる」「非同期の処理やAPIを作る」 「(そもそも)複数のサービスをローカルで動かす」 👉 制約をつくることで、gRPC service から新しいサービスをscaffoldできる! 👉 Connectなので雑cURLデバッグも可能! 👉 ローカルではサービスを1つにまとめることで、いろんな複雑さから開放される!

Slide 36

Slide 36 text

Connectで非同期APIも楽にする 楽にしたい!

Slide 37

Slide 37 text

© LayerX Inc. 37 非同期API・非同期ジョブ Connectで非同期APIも楽にする ▸ だいたい必要になる ‐ ここで扱うのはscheduledなものではなく、on-demandなもの。 e.g. 通知送信 Amazon SQS等の外部のキューを利用するなどして実装する ▸ デバッグが面倒になりがち ‐ テストでカバーできるならそれでもいいけど、 外部連携等あるとつなぎ込んでの動作確認をやりたくなりがち… ‐ REPLがない言語で非同期処理をどうやって雑にエンキューするか問題

Slide 38

Slide 38 text

© LayerX Inc. 38 非同期API・非同期ジョブにもConnectを Connectで非同期APIも楽にする Publisher Connect server service GreetService { rpc Greet(GreetRequest) returns (GreetResponse) {} } 行き先(GreetService/Greet)と ペイロード(GreetRequest)を キューに詰める Subscriber Worker

Slide 39

Slide 39 text

© LayerX Inc. 39 非同期API・非同期ジョブにもConnectを Connectで非同期APIも楽にする Publisher Connect server Subscriber Worker キューから取り出した内容を見て Subscriber が適切な Connect Server を呼ぶ ここがConnectによる通信になる

Slide 40

Slide 40 text

© LayerX Inc. 40 この仕組みがなぜ嬉しいか Connectで非同期APIも楽にする Publisher Connect server Subscriber Worker ▸ 非同期でもちゃんとスキーマ定義できる ‐ 雑に json.Marshal するのではなく、 同期APIと同じメンタルモデルで型定義を。 非同期ジョブ・非同期APIもAPI! ▸ 雑デバッグが楽! ‐ cURL最高!

Slide 41

Slide 41 text

© LayerX Inc. 41 Protobuf + Connect 活用ポイント③ Protobuf と Connect 「隣のサービスから叩かれるAPIを作る」「隣のサービスのAPIを叩く」 「隣のプロダクトの情報をフロントで見せる」「非同期の処理やAPIを作る」 「(そもそも)複数のサービスをローカルで動かす」 👉 非同期APIでもProtobuf+Connectによるスキーマ定義や実装を使い デバッグの手間や認知負荷を軽減する 型を信用できるようにもなる!

Slide 42

Slide 42 text

フロントから横のプロダクトの情報を見たい! これはProtobufをいい感じにやります

Slide 43

Slide 43 text

© LayerX Inc. 43 おさらい: 隣のプロダクトの情報をフロントで見みたい 隣のプロダクトの持つ情報をフロントで見たい Service 組織情報など Service カード明細など Service 申請情報など Webapp バクラク申請 ▸ 連携パターンが増えると、 この要求も増える ▸ プロダクトごとのBFFを作る? ‐ たとえば「ユーザ情報見たい」とかは 全プロダクト共通だけど、それも毎回 BFFに作る? ▸ データの連携 → 関連するデータの つながりを引っ張ってこれればよい

Slide 44

Slide 44 text

© LayerX Inc. 44 アイディア: 1つのGraphQLスキーマに集約してしまう 隣のプロダクトの持つ情報をフロントで見たい Service 組織情報など Service カード明細など Service 申請情報など GraphQL Gateway バクラク申請 Webapp バクラクの持つ情報を1つのグラフで表現 隣のプロダクトの情報がほしいときはグラフをつなげる

Slide 45

Slide 45 text

© LayerX Inc. 45 問題: GraphQLとConnect(Protobuf)でスキーマが分裂する 隣のプロダクトの持つ情報をフロントで見たい Service カード明細など GraphQL Gateway message CardTransaction { // 決済金額 int32 amount = 1; } type CardTransaction { """決済金額""" amount: Int! } フィールドの更新追従も面倒だが、ドキュメントの同期とかは考えたくない… ドキュメントを書くモチベーションも下がってしまう

Slide 46

Slide 46 text

© LayerX Inc. 46 対応: ProtobufからGraphQLの型を生成する 隣のプロダクトの持つ情報をフロントで見たい message CardTransaction { // 決済金額 int32 amount = 1; } type CardTransaction { """決済金額""" amount: Int! } GraphQLのObjectやEnumなどの型はProtobufから自動生成することで、同期の手間を省く Protobufはコード生成ツールを自作するのも比較的カンタン .proto .graphql * gRPC → GraphQL 変換だとGraphQL MeshなどのOSSもありますが、 今回は@izumin5210が自作したprotoc-gen-pothosを使いました

Slide 47

Slide 47 text

© LayerX Inc. 47 Protobuf + Connect 活用ポイント③ Protobuf と Connect 「隣のサービスから叩かれるAPIを作る」「隣のサービスのAPIを叩く」 「隣のプロダクトの情報をフロントで見せる」「非同期の処理やAPIを作る」 「(そもそも)複数のサービスをローカルで動かす」 👉 情報を1つのGraphQLスキーマに集約!型はProtobufから自動生成!

Slide 48

Slide 48 text

まとめ

Slide 49

Slide 49 text

© LayerX Inc. 49 まとめ ▸ コンパウンドプロダクト(複数プロダクト)環境で開発がしんどくなっていく問題を、 ProtobufとConnectの力で軽減するようにした ‐ プロダクトの質・コードの質を高く保つ、でもスピードもなるべく落とさず、スケールしていく! ▸ 具体的な5つの問題を軽減するために、ConnectとProtobufを活用した ‐ 「隣のサービスから叩かれるAPIを作る」「隣のサービスのAPIを叩く」 ‐ Connect(gRPC)のコード生成による安全かつ生産性の高いAPI開発 ‐ 「(そもそも)複数のサービスをローカルで動かす」 ‐ 論理的なサーバ単位と物理的なサーバ単位を分けて考え、ローカルでは1つのサーバに集める ‐ 「非同期の処理やAPIを作る」 ‐ キューは挟みつつ、非同期APIもConnectで実装することでデバッグの手間や認知負荷を軽減 ‐ 「隣のプロダクトの情報をフロントで見せる」 ‐ 集約層としてGraphQLを導入しつつ、スキーマ2重定義を避けるためにコード生成を使う

Slide 50

Slide 50 text

© LayerX Inc. 50 おまけ: Protobuf その他の活用 Protobuf と Connect ▸ ログ用に効率の良い ObjectMarshaler を生成 ▸ FeatureFlagをProtobufで定義 ▸ ConnectのRPCハンドラのscaffolding ▸ ちょっと便利なgRPC clientのコンストラクタを生成 ‐ gRPCサービスのURLを解決する仕組みが入ってたり, 共通のinterceptorが入っていたり… ▸ … ほかにもいろいろ!

Slide 51

Slide 51 text

© LayerX Inc. 51 We are hiring! layerx.go #0 LayerX Open Door 今日の内容で気になり・ツッコミ・議論したいことがあった方! カジュアル延長戦もお待ちしております 🙏 layerx.go 5/24(金) 19:00〜20:00 @ onlineにて実施します。 「Goの情報収集や知見の共有活動 / Goのプロダクトコー ドのキャッチアップ」というテーマで複数のLT(公募含む)を 発表予定です!