Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Goで実践するBFP
Search
Terry
January 18, 2025
Technology
1
140
Goで実践するBFP
2025/01/18に開催されたGopher's Gatheringの20minセッションです
https://connpass.com/event/329963/
Terry
January 18, 2025
Tweet
Share
More Decks by Terry
See All by Terry
scratch imageでのtime.Location()
hiroyaterui
0
9
goroutineで親のctxのkey/valueを引き継ぐ実装
hiroyaterui
0
140
Go 1.20で入った Wrapping multiple errorsをみてみる
hiroyaterui
0
110
intSize = 32 << (^uint(0) >> 63)とは
hiroyaterui
1
590
リモート開発でのコミュニケーションどうしてますか?
hiroyaterui
0
130
POSレジとGo
hiroyaterui
0
290
プリンシプルオブプログラミング ~3章(Unix除く)と7章~
hiroyaterui
0
190
データ連携2ヶ月
hiroyaterui
0
36
はじめてのIT勉強会2018_4_25
hiroyaterui
0
320
Other Decks in Technology
See All in Technology
DeepSeek on AWS
hariby
1
180
地方企業がクラウドを活用するヒント
miu_crescent
PRO
1
120
Kubernetes x k6 で負荷試験基盤を開発して 負荷試験を民主化した話 / Kubernetes x k6
sansan_randd
0
480
Fin-JAWS第38回reInvent2024_全金融系セッションをライトにまとめてみた
mhrtech
1
160
これからSREになる人と、これからもSREをやっていく人へ
masayoshi
3
3k
Zenn のウラガワ ~エンジニアのアウトプットを支える環境で Google Cloud が採用されているワケ~ #burikaigi #burikaigi_h
kongmingstrap
19
7.2k
パブリッククラウドのプロダクトマネジメントとアーキテクト
tagomoris
4
920
Amazon Aurora バージョンアップについて、改めて理解する ~バージョンアップ手法と文字コードへの影響~
smt7174
1
330
The 5 Obstacles to Empowered Teams - Twice the Value in Half the Time
mdalmijn
0
190
トレードオフスライダーにおける品質について考えてみた
suzuki_tada
3
200
カスタムインストラクションでGitHub Copilotをカスタマイズ!
07jp27
8
1.5k
生成AIを活用した機能を、顧客に提供するまでに乗り越えた『4つの壁』
toshiblues
2
270
Featured
See All Featured
No one is an island. Learnings from fostering a developers community.
thoeni
20
3.1k
Unsuck your backbone
ammeep
669
57k
Making Projects Easy
brettharned
116
6k
How STYLIGHT went responsive
nonsquared
98
5.3k
Building Applications with DynamoDB
mza
93
6.2k
Designing for humans not robots
tammielis
250
25k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
7
620
How to Think Like a Performance Engineer
csswizardry
22
1.3k
Navigating Team Friction
lara
183
15k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
666
120k
Adopting Sorbet at Scale
ufuk
74
9.2k
Side Projects
sachag
452
42k
Transcript
Goで実践するBFP -backend for product- 2025/01/18 Gopher’s Gathering
©Showcase Gig 自己紹介 • 照井寛也 • 株式会社Showcase Gig ◦ エンジニアリングオフィス
• Sendai.go オーガナイザー • X : @10_ru_1
©Showcase Gig • BFP導入のきっかけと背景 • BFPとは • BFP導入に向けて • BFPをGoで実装する
• BFPの効果 セッションの概要
©Showcase Gig BFP導入のきっかけと背景
Confidential 提供プロダクト 店内モバイルオーダー テイクアウトモバイルオーダー 次世代タッチパネル型 注文決済端末
©Showcase Gig プロダクト構成 Backend Backend Backend Frontend Frontend Frontend
Platform gRPC gRPC gRPC
©Showcase Gig Platformの目指す姿 • 注文・会計・決済・マスタデータを持つ • 共通マスタから様々な飲食形態に対応する • 各プロダクトからの注文・会計データを集約
し、データを利活用できる状態にする Platform 注文 会計 決済 マスタ
©Showcase Gig Platformの目指す姿 飲食形態(プロダクト)に依存しないAPI設計
©Showcase Gig プロダクトに依存しないAPI = primitiveなAPI // どのプロダクトでも共通して使われるデータモデル message Menu
{ uint64 menu_id = 1; uint64 restaurant_id = 2; string name = 3; repeated Item items = 4; } message Item { uint64 item_id = 1; uint64 restaurant_id = 2; string name = 3; string description = 4; uint32 price = 5; repeated Allergen Allergens = 6; }
©Showcase Gig プロダクトに依存しないAPI設計 • APIの抽象度が高く以下の問題が生じてきた ◦ primitiveなAPIの特性上、複数回呼び出す必要がある ▪ レイテンシーの悪化
▪ インフラコストの増加 ◦ primitiveなAPI故、プロダクトの処理には必要のないデータも含まれる ▪ 導入企業のマスタによっては、gRPCのデータサイズ上限の4MBを突破
©Showcase Gig 実際に生じた問題 💥 • シチュエーション ◦ 店員さんが商品を品切れにしたい • 設計の課題
◦ Platform側が提供しているAPIは、あくまで「メニューの取得」のみ ◦ メニュー取得のAPIは、商品情報全てを返している • 問題 ◦ プロダクト側では「注文できる商品の一覧」が欲しいにもかかわらず、まず全メニューを取得し なくてはならない ▪ メニュー内の商品が重複している場合は、プロダクト側で重複を弾く実装が必要 ▪ 品切れ画面に不要な情報も含まれる • メニューのデータ量が多い店舗ではデータが 4MBを超える
©Showcase Gig プロダクトに依存しないAPI = primitiveなAPI // どのプロダクトでも共通して使われるデータモデル message Menu
{ uint64 menu_id = 1; uint64 restaurant_id = 2; string name = 3; repeated Item items = 4; ←この情報欲しい } message Item { uint64 item_id = 1; ←この情報欲しい uint64 restaurant_id = 2; ←この情報欲しい string name = 3; ←この情報欲しい string description = 4; uint32 price = 5; repeated Allergen Allergens = 6; }
©Showcase Gig 実際に生じた問題 💥 func (a *allItemUsecase) ListByMenuIds( ctx context.Context,
in ListAllItemItemsByMenuIdsInput ) (*ListAllItemItemsByMenuIdsOutput, error) { // 4MBを超えることがある menus, err := a.pfMenuRepository.List(ctx, in.MenuIds) if err != nil { return nil, api_error.NewInternalError(err, "failed to list menus from pf") } if menus == nil { return nil, api_error.NewResourceNotFoundError("not found menus") } itemMap := make(map[types.ItemID]entity.Item,0) for _,menu := menus{ // ここでmenusのデータを解析し、itemMapに必要な情報を格納する処理が必要 } …. }
©Showcase Gig 戦略の天秤 Platform 戦略 プロダクト 最適化
©Showcase Gig GraphQLの選択肢 • GraphQLは以下から選択を見送った ◦ レイテンシー ▪ NWを挟むことによるレイテンシーの悪化
◦ Github Repositoryの増加 ▪ コードの増加以上に認知負荷が増す可能性がある • repositoryの移動、protoの取り込み、CI/CD….. ▪ インフラコストの増大 ◦ GraphQLの学習コスト ▪ BFPであれば既存のgRPCの知識で達成可能 Backend Frontend Platform gRPC GraphQL
©Showcase Gig BFP
©Showcase Gig BFP(Backend For Product) O:der Productが使いやすいAPIを提供することを目的とする。具体的には以下。 • O:der
Productが欲しいデータを集めたEPの提供(集約・統合API) ◦ platformのentityを跨いでデータを返す・永続化する ▪ 例)AとBを呼んでO:der Productに必要なデータを形成しているが、それを1回で呼べるようにする ◦ 注文・会計・決済をまとめて行うEPの提供 • O:der Productが高いパフォーマンス(データ量やレイテンシー)を出せるEPの提供(絞り込みAPI) ◦ O:der Productに必要なデータのみを返す ▪ 例)メニューに紐づく商品を重複なしで返す
©Showcase Gig Before After BFP(Backend For Product) domain a
usecase a controller a usecase A domain b usecase b controller b platform domain a bfp usecase A bfp controller A usecase A domain b platform
©Showcase Gig Platform 飲食形態(プロダクト)に依存しないAPI設計 & BFP
©Showcase Gig platformの責務が拡大した 対話
©Showcase Gig platformの責務が拡大した 対話&対話
©Showcase Gig BFPをGoで実装する
©Showcase Gig BFPをGoで実装する • Platformのアーキテクチャ entity repository IF usecase
query service IF controller repository impl query service impl
©Showcase Gig • 既存のprimitiveなAPIに影響を与えない構成 • domainに対して変更を加えることはしない • listの場合... ◦ bfp
controller→(usecase→) bfp query_service • createの場合 ◦ bfp controller→bfp usecase→repository BFPをGoで実装する app ├── domain │ ├── model │ ├── repository │ └── service ├── infrastructure │ ├── repository │ └── query_service │ ├── menu_query_service │ ├── … │ └── bfp ├── controller │ ├── accounting_controller │ ├── … │ └── bfp ├── usecase │ ├── accounting_usecase │ ├── … │ └── bfp proto ├── platform └── bfp
©Showcase Gig List処理(メニュー取得)の例 func (t *tableMenuController) ListUniqueItemsByMenuIds( ctx context.Context,
in *tableApi.ListUniqueItemsByMenuIdsRequest ) (*tableApi.ListUniqueItemsByMenuIdsResponse, error) { // usecaseを意図的にスキップ(query_serviceの呼び出しだけのため) response, err := t.tableMenuQueryService.ListUniqueItemsByMenuIds(ctx, in.MenuIds) if err != nil { return nil, api_error.NewInternalError(err, "failed to ListUniqueItemsByMenuIds") } if response == nil { return nil, api_error.NewResourceNotFoundError("not found ListUniqueItemsByMenuIds") } return response, nil } controller層
©Showcase Gig List処理(メニュー取得)の例 func (t *tableMenuQueryService) ListUniqueItemsByMenuIds( ctx context.Context,
menuIds []uint64 ) (*tableApi.ListUniqueItemsByMenuIdsResponse, error) { readQueryable := t.clients.ReadReplicaClient.ReadQueryable(ctx) query, params, err := sqlx.In("SELECT "+t.dao.MenuColumns()+" FROM "+t.dao.MenuTableName()+" WHERE `id` in (?) AND status != ?", menuIds, menu_entity.MenuStatusArchived) if err != nil { return nil, fmt.Errorf("failed to build query: %w", err) } // menuを元に、紐づく商品を取得するクエリ …… } infra層(query_service IFの実装)
©Showcase Gig Create処理(統合作成API)の例 • シチュエーション ◦ O:der Togo(テイクアウト)のプロダクトで、注文と会計伝票の作成は同時に行われる ▪
O:der Table(店内飲食)は(複数の)注文が行われ、その後別リクエストで会計伝票を作成する • 実装 ◦ Platform側が提供しているAPIは、「注文の作成」「会計伝票の作成」それぞれ独立している ◦ プロダクト側では1つのトランザクション内で「注文の作成」「会計伝票の作成」を行う必要がある ▪ 場合によっては値引きの考慮もする • 課題 ◦ 最低でも2回のEPを呼び出す必要があり、トランザクションの管理が必要になる ▪ NWを経由するためにレイテンシーの悪化が懸念される
©Showcase Gig Create処理(統合作成API)の例 func (c *orderToAccountingController) CreateV1( ctx context.Context,
request *pb.CreateV1Request ) (*pb.CreateV1Response, error) { output, err := c.createUseCase.Create(ctx, order_to_accounting_usecase.CreateInput{ … }) if err != nil { return nil, api_error.NewInternalError(err, "failed to create resources due to internal error") } return &pb.CreateV1Response{ OrderId: uint64(output.OrderID), AccountingVoucher: accounting_controller.NewVoucher(output.Voucher) }, nil } controller層
©Showcase Gig Create処理(統合作成API)の例 func (uc *createUseCase) Create(ctx context.Context, input
CreateInput) (*CreateOutout, error) { err := input.validate() if err != nil{ return err } orderEntity,err := entity.NewOrderEntity(....) if err != nil{ return err } accountingEntity,err := entity.NewAccountingEntity(orderEntity,....) if err != nil{ return err } usecase層(その1)
©Showcase Gig Create処理(統合作成API)の例 // transaction管理内で行える err = uc.dbClients.WritableClient.RunInWriteTx(ctx, func(context
context.Context, queryable queryable.WriteQueryable) error { // 注文の永続化 err = uc.orderRepository.Persist(ctx, queryable, orderEntity, orderedAt.Time) if err != nil { return fmt.Errorf("failed to persist order: %w", err) } // 会計伝票の永続化 err = uc.voucherRepository.Persist(ctx, queryable, voucher) if err != nil { return fmt.Errorf("failed to persist voucher: %w", err) } } …… } usecase層(その2)
©Showcase Gig BFPの効果
©Showcase Gig BFPの効果 • メニュー取得・統合作成APIともにProduct EPからみたレイテンシーの改善が見られた • 特に、メニュー取得APIに関しては、以下の成果が出た ◦
EP処理を4327ms→501msに改善→速度を8倍に改善 ◦ 42.9MBだったレスポンスを1.9MBに削減→レスポンスサイズを 1/20に改善 ▪ また、gPRCのデフォルトの4MBに収まるサイズにまで短縮
©Showcase Gig BFPの今後 • BFPを活用することでプロダクトのレイテンシーをさらに改善し、エンドユーザーの体験をより 良くする • BFPが増えると、platformの責務・処理が増えていく ◦
各ドメインの責務を維持しつつ、疎結合を意識した設計を継続していきたい
©Showcase Gig BFPの今後 Platform 戦略 プロダクト 最適化
©Showcase Gig Thank you