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
89
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
Git scrapingで始める継続的なデータ追跡 / Git Scraping
ohbarye
4
110
アジャイルチームが変化し続けるための組織文化とマネジメント・アプローチ / Agile management that enables ever-changing teams
kakehashi
3
3k
Oracle Exadata Database Service(Dedicated Infrastructure):サービス概要のご紹介
oracle4engineer
PRO
0
12k
20240513 - 框裡框外_文學院學生如何在AI世代安身立命 @ 淡江大學
dpys
0
640
商品レコメンドでのexplicit negative feedbackの活用
alpicola
1
210
rootful・rootless・privilegedコンテナの違い/rootful_rootless_privileged_container_difference
moz_sec_
0
130
Oracle Base Database Service 技術詳細
oracle4engineer
PRO
6
54k
ネットワーク可視化の世界
likr
7
5.7k
.NET 最新アップデート ~ AI とクラウド時代のアプリモダナイゼーション
chack411
0
170
JAWS-UG20250116_iOSアプリエンジニアがAWSreInventに行ってきた(真面目編)
totokit4
0
110
スケールし続ける事業とサービスを支える組織とアーキテクチャの生き残り戦略 / The survival strategy for Money Forward’s engineering.
moneyforward
0
250
20240522 - 躍遷創作理念 @ PicCollage Workshop
dpys
0
310
Featured
See All Featured
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
3
240
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
3
350
Designing on Purpose - Digital PM Summit 2013
jponch
116
7.1k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
356
29k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
Imperfection Machines: The Place of Print at Facebook
scottboms
267
13k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
127
18k
Mobile First: as difficult as doing things right
swwweet
222
9k
Agile that works and the tools we love
rasmusluckow
328
21k
How to train your dragon (web standard)
notwaldorf
89
5.8k
Testing 201, or: Great Expectations
jmmastey
41
7.2k
Fireside Chat
paigeccino
34
3.1k
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