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
91
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
570
リモート開発でのコミュニケーションどうしてますか?
hiroyaterui
0
130
POSレジとGo
hiroyaterui
0
290
プリンシプルオブプログラミング ~3章(Unix除く)と7章~
hiroyaterui
0
170
データ連携2ヶ月
hiroyaterui
0
36
はじめてのIT勉強会2018_4_25
hiroyaterui
0
320
Other Decks in Technology
See All in Technology
東京Ruby会議12 Ruby と Rust と私 / Tokyo RubyKaigi 12 Ruby, Rust and me
eagletmt
1
390
AWS re:Invent 2024 ふりかえり勉強会
yhana
0
710
KMP with Crashlytics
sansantech
PRO
0
180
コロプラのオンボーディングを採用から語りたい
colopl
5
640
プロダクト組織で取り組むアドベントカレンダー/Advent Calendar in Product Teams
mixplace
0
680
Godot Engineについて調べてみた
unsoluble_sugar
0
200
Unlearn Product Development - Unleashed Edition
lemiorhan
PRO
2
170
Azureの開発で辛いところ
re3turn
0
220
Accessibility Inspectorを活用した アプリのアクセシビリティ向上方法
hinakko
0
150
Fearsome File Formats
ange
0
580
CDKのコードレビューを楽にするパッケージcdk-mentorを作ってみた/cdk-mentor
tomoki10
0
120
comilioとCloudflare、そして未来へと向けて
oliver_diary
5
400
Featured
See All Featured
GraphQLの誤解/rethinking-graphql
sonatard
68
10k
Unsuck your backbone
ammeep
669
57k
Reflections from 52 weeks, 52 projects
jeffersonlam
348
20k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
44
7k
What’s in a name? Adding method to the madness
productmarketing
PRO
22
3.2k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
251
21k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
47
5.1k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
280
13k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
44
9.4k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
29
950
YesSQL, Process and Tooling at Scale
rocio
170
14k
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