Share
Embed
ホットペッパービューティーにおけるモバイルアプリ向けAPIのBFF/Backend分割 / JJUG2019Spring
JJUG CCC 2019 Springの登壇資料です。
http://www.java-users.jp/ccc2019spring/#/sessions/7fbef8cc-bf8d-4855-8534-c7fca118fb1d
Transcript
82 slides
-
⾃⼰紹介 • Nawate Ippei • リクルートライフスタイル • 2016/04に新卒⼊社 • ⼊社してからずっとホットペッパービューティー
• 仕事内容 • 2016/04 ~ 2018/01 Androidエンジニア • 2018/01 ~ サーバサイドエンジニア https://twitter.com/sakuna63 https://github.com/sakuna63 -
今⽇のお話について • モバイルアプリ向けAPIを2つのAPI層(BFF層/Backend層)に分割した取り組み についてお話しします • 現在進⾏系で分割中です 👷 🚧(約100本中、28本のAPIが新構成に移⾏済み) •
新構成のAPIを利⽤したアプリがつい先⽇(5/13)リリースされました • なので保守・運⽤の経験に基づいて語れることはほぼありません 🙇 • ࠓׂ։͔࢝ΒϦϦʔε·ͰͷؒʹಘͨݟΛڞ༗͠·͢ -
アジェンダ 1. BFFとは何か? 2. BFF/Backend分割の⽬的 3. BFF/Backend分割の⽅針 4. BFF/Backendの設計・実装 •
API設計 • 技術選定 • ソフトウェア設計・実装 5. BFF/Backend構成の開発体制 6. まとめ -
アジェンダ 1. BFFとは何か? 2. BFF/Backend分割の⽬的 3. BFF/Backend分割の⽅針 4. BFF/Backendの設計・実装 •
API設計 • 技術選定 • ソフトウェア設計・実装 5. BFF/Backend構成の開発体制 6. まとめ -
BFFとは何か? • BFF = Backends For Frontends • マイクロサービスアーキテクチャの1つ •
FrontendとAPI Serviceの間に位置するBackendサーバ • 個々のサービスが提供するAPIを集約し、Frontendに特化したレスポンスを返す API Service API Service API Service JSON HTML CSS, JS BFF for Web BFF for App Search Engine Mobile App Browser -
今回のBFFの⽤途:システムの関⼼事の分離 • 表⽰処理を⾏う層(SOE)とデータを管理する層(SOR)の分離 • BFFは前者 • 本発表のBFF/Backend分割はこの⽤途に近い BFF (for
SOE) Monolithic Backend Backend (for SOR) Frontend Frontend -
アジェンダ 1. BFFとは何か? 2. BFF/Backend分割の⽬的 3. BFF/Backend分割の⽅針 4. BFF/Backendの設計・実装 •
API設計 • 技術選定 • ソフトウェア設計・実装 5. BFF/Backend構成の開発体制 6. まとめ -
背景と課題 • API開発のスピードがアプリ改善スピードのボトルネックになっていたため • APIの開発はアプリチームからの依頼ベースで実施されるが、 リソース調整などの要因で開発完了まで3ヶ⽉以上かかっていた • アプリチームの開発サイクルはより早く(1ヶ⽉)、API開発スピードがボトルネックに... モバイルアプリ
向けAPI Webアプリ API開発依頼 開発完了 (3ヶ⽉~後) アプリチーム(8名) サーバサイドチーム(数⼗名) モバイルアプリ -
課題に対する打ち⼿ BFF層 Backend層 モバイルアプリ向けAPI (モノリシック) サーバサイドチーム アプリチーム サーバサイドチーム 分割
APIのBFF/Backend分割を実施し アプリチームのリソースでAPIを開発・リリースできる体制を整えることで ボトルネック(API開発スピード)の改善を狙う -
アジェンダ 1. BFFとは何か? 2. BFF/Backend分割の⽬的 3. BFF/Backend分割の⽅針 4. BFF/Backendの設計・実装 •
API設計 • 技術選定 • ソフトウェア設計・実装 5. BFF/Backend構成の開発体制 6. まとめ -
BFF/Backend分割の⽅針 ① 各層にどう処理を分担させるか ② 層間の通信をどのように⾏うか BFF層 Backend層 ②どう通信? モバイルアプリ向けAPI
(モノリシック) 処理A 処理C 処理B 処理D ①どう分担? ? ? ? ? -
このように分割することに BFF層 Backend層 表⽰要件に関わる処理 業務ロジック アプリ特有の処理 データソース(DB等) のRead/Write REST
API • BFFにアプリ特有の処理や表⽰要件に関わる処理を持たせる • BackendはREST APIを提供、BFFは複数のBackend APIを呼び出し結果を集約 -
BFF層 Backend層 モバイルアプリ向けAPI (モノリシック) 分担 処理A 処理C 処理B 処理D
? ? ? ? ① 各層にどう処理を分担するか? 🤔 -
処理分担のポイント • BFFはアプリチームで⾼速に開発できるようにしたい • 開発(変更)の早さ ≒ 意思決定の早さ(特に⼤きな組織では) • すばやく意思決定するには
「他チーム・他システムへの依存・影響を⼩さくする」のが⼤事 -
処理分担のポイント • BFFはアプリチームで⾼速に開発できるようにしたい • 開発(変更)の早さ ≒ 意思決定の早さ(特に⼤きな組織では) • すばやく意思決定するには
「他チーム・他システムへの依存・影響を⼩さくする」のが⼤事 アプリチームで変更の意思決定が完結する処理をBFFに持たせる -
処理分担の基準 BFF層 Backend層 表⽰要件に関わる処理 • プレゼンテーションロジック • 表⽰要件に合わせたデータ集約 業務ロジック アプリ特有の処理
• アクセストークンの管理など データソース(DB, 検索エンジン) のRead/Write -
補⾜:表⽰要件に合わせたデータ集約 データA, B, Cを まとめて表⽰したい! (表⽰要件) 表⽰要件に合わせて、複数のデータをAPIが⼀つに集約して返すこと API
A B C API A B C API A B C 集約 モバイルアプリ -
補⾜:表⽰要件に合わせたデータ集約の例 • 「予約」「メニュー」「クーポン」を同時に表⽰ • これらは独⽴したデータ • APIが↓↓のように集約して返している 予約⼀覧 予約 予約先サロンのメニューとクーポン
"reservations": [ { "coupons": [ ... ], "menus": [ ... ] } ] -
Backend BFF/Backend構成におけるデータ集約 • BFFは複数のBackend APIを組み合わせてデータを集約する • ⾃由に組み合わせられるよう、Backend APIは再利⽤しやすい粒度(REST)で作成 BFF A
B C API A B C API A B C モバイルアプリ 集約 REST API -
BFF/Backend分割イメージ - Before モバイルアプリ向けAPI (モノリシック) サーバサイドチーム アプリチーム 表⽰要件に関わる処理 •
プレゼンテーションロジック • 表⽰要件に合わせたデータ集約 業務ロジック データソース(DB, 検索エンジン) のRead/Write アプリ特有の処理 • アクセストークンの管理など -
BFF/Backend分割イメージ - After サーバサイドチーム アプリチーム BFF層 Backend層 REST API 表⽰要件に関わる処理
• プレゼンテーションロジック • 表⽰要件に合わせたデータ集約 業務ロジック データソース(DB, 検索エンジン) のRead/Write アプリ特有の処理 • アクセストークンの管理など -
分割⽅針の振り返り 処理分担の基準について • 基本的に、この基準で迷いなく開発できている 👍 • ⼀点悩んだところがあったので次のスライドで紹介 • BFFの開発スピードが早くなるかはこれから検証 🧐(保守・運⽤が始まったばかりなので)
BackendのAPI粒度(REST)について • 参照系のBackend API(GET)の四分の⼀(24本中6本)がBFF API間で再利⽤できている 👍 • 逆に更新系の Backend API(POST, PUT, DELETE)はBFF APIと1:1で対応することがほとんど -
分割⽅針の振り返り 処理分担の基準について • 基本的に、この基準で迷いなく開発できている 👍 • ⼀点悩んだところがあったので次のスライドで紹介 • BFFの開発スピードが早くなるかはこれから検証 🧐(保守・運⽤が始まったばかりなので)
BackendのAPI粒度(REST)について • 参照系のBackend API(GET)の四分の⼀(24本中6本)がBFF API間で再利⽤できている 👍 • 逆に更新系の Backend API(POST, PUT, DELETE)はBFF APIと1:1で対応することがほとんど -
悩みどころ:プレゼンテーションと業務の境界 • ドメインモデルが表⽰制御のプロパティを持つことがある • 例えば「値段にチルダ(~)をつけるフラグ」というのがある • こういったときのテキスト加⼯処理など、どの層に持たせるべきか悩む 🤔 • ϓϨθϯςʔγϣϯϩδοΫͱଊ͑Δ͖͔、業務ロジックと捉えるべきか...
public class Price { private int value; /** * 値段にチルダ(~)を付けるかどうかのフラグ */ private boolean tildaSuffixRequired; } -
アジェンダ 1. BFFとは何か? 2. BFF/Backend分割の⽬的 3. BFF/Backend分割の⽅針 4. BFF/Backendの設計・実装 •
API設計 • 技術選定 • ソフトウェア設計・実装 5. BFF/Backend構成の開発体制 6. まとめ -
アプリのユースケース単位とは? 🤔 • 右画⾯で⾔う⾚枠の単位 • これらの単位でAPIを作成 ネイルカタログ特集を⾒る ネイルデザインランキングを⾒る 新着ネイルデザイン
を⾒る ネイルカラーを⾒る ネイルテイスト を⾒る ネイルシーンを⾒る -
画⾯単位で作る難点 Fault Tolerance観点 • ⼀部の不安定なBackend APIに、BFF API全体のパフォーマンスや成否が引きずられる アプリからの使いやすさ • キャッシュ要件の異なるリソースが混ざるときなど、ハンドリングが複雑になる
• etc... このような事情も踏まえてユースケース単位で作成している -
RESTにこだわり過ぎないことが⼤事 集約処理をBackend(SQL)でやったほうが圧倒的に楽なときはBackendに任せる • 異なるリソースまたいだソート + ページネーション処理など API全体のパフォーマンス影響が⼤きいとき • API版のN+1問題は必ず避ける(BFFの⽤途に合わせてBackend APIを作る)
• リソースのうち、計算負荷の⾼いプロパティはBFFから任意取得できるようにする • Backend APIに fi eldsパラメータを設けるなど -
RESTにこだわり過ぎないことが⼤事 集約処理をBackend(SQL)でやったほうが圧倒的に楽なときはBackendに任せる • 異なるリソースまたいだソート + ページネーション処理など API全体のパフォーマンス影響が⼤きいとき • API版のN+1問題は必ず避ける(BFFの⽤途に合わせてBackend APIを作る)
• リソースのうち、計算負荷の⾼いプロパティはBFFから任意取得できるようにする • Backend APIに fi eldsパラメータを設けるなど BFFとBackend、互いの歩み寄りが⼤事 💪 -
BFFのAPIバージョニング • モバイルアプリ向けAPIは後⽅互換性のサポートが必要 • アプリはユーザがアップデートしない限り、古いバージョンが使われ続ける • よって、古いバージョンのAPIも使われ続ける可能性がある • バージョン指定はAcceptヘッダで⾏う⽅式を採⽤ •
Accept:application/vnd.hpb-app-BFF.v1.0+json • バージョンアップは限局分岐(APIごと)で実施 -
アジェンダ 1. BFFとは何か? 2. BFF/Backend分割の⽬的 3. BFF/Backend分割の⽅針 4. BFF/Backendの設計・実装 •
API設計 • 技術選定 • ソフトウェア設計・実装 5. BFF/Backend構成の開発体制 6. まとめ -
技術選定 BFF Backend ⾔語 Kotlin 1.3 AdoptOpenJDK 11 アプリケーションフレームワーク Sprint
Boot2 Spring5系(WebFlux) Sprint Boot2 Spring5系(WebMVC) -
技術選定 BFF Backend ⾔語 Kotlin 1.3 AdoptOpenJDK 11 アプリケーションフレームワーク Sprint
Boot2 Spring5系(WebFlux) Sprint Boot2 Spring5系(WebMVC) -
BFFの⾔語選定 • ⾔語はアプリエンジニアの使い慣れたものにしたいのでKotlinかSwiftの⼆択 • サーバサイドSwiftはまだあまり聞かないのでリスキー 😨 • KotlinならサーバサイドJavaの資産を活⽤できる! 🙆 •
組織的にもJava(JVM)の開発・運⽤知⾒が多い 🙆 Kotlinを採⽤! -
BFFのフレームワーク選定 • ⾔語はKotlinでフレームワークは実質Spring⼀択で考えた • ⾔語にこだわらなければnode.jsやGolangも候補だった • Springは5.0からKotlinを正式にサポート 🙆 • Spring
WebFluxならNon-Blocking I/Oのサポートも 🙆 • Spring WebFluxはReactorの学習コストが懸念だが、 AndroidチームがRxJavaを利⽤していたので⼤丈夫そう 🙆 Spring WebFluxを採⽤! -
BFFの技術選定の振り返り • 「ほんとにアプリチームでAPI開発できるのか?」という懸念はあったが、 問題なく開発できそうなことがわかってきている • ほとんどKotlinとReactorの知識だけで書ける • Springは下回りさえ整えてしまえば、API実装で意識することはほぼない •
特にAndroidエンジニアにとってはかなりハードルが低そう • iOSエンジニアについては要検証... • 実際Androidエンジニアに実装してもらったが 「これならいけそう」という感想を得ている 👏 -
BFFの技術選定の振り返り • Kotlin x Springは違和感なく開発できている 😄 • 「KotlinだからSpringが活かせない」ということは全くない • 「SpringだからKotlinが活かせない」とういことも全くない
• 個⼈的に気に⼊ってるポイント • SpringはNonnull/Nullableがしっかりアノテートされているので、 KotlinのNull安全性が活きる 💪 • Spring公式提供の拡張関数群も充実 • 今後は、spring-fu/kofuのようなDSLにも期待 -
技術選定 BFF Backend ⾔語 Kotlin 1.3 AdoptOpenJDK 11 アプリケーションフレームワーク Sprint
Boot2 Spring5系(WebFlux) Sprint Boot2 Spring5系(WebMVC) -
Backendの技術選定 • あまりこった検討はなし • Java • 要員確保の観点 • 組織的にもJavaエンジニアが多い 🙆
• Spring Boot + Sprint WebMVC • 社内事例のあった技術スタックを素直に採⽤ -
アジェンダ 1. BFFとは何か? 2. BFF/Backend分割の⽬的 3. BFF/Backend分割の⽅針 4. BFF/Backendの設計・実装 •
API設計 • 技術選定 • ソフトウェア設計・実装 5. BFF/Backend構成の開発体制 6. まとめ -
ソフトウェアアーキテクチャ Backend プレゼンテーション層 ドメイン層 インフラストラクチャ層 アプリケーション層 プレゼンテーション層 ドメイン層 インフラストラクチャ層 アプリケーション層
通常のWebアプリ プレゼンテーション層 ドメイン層 インフラストラクチャ層 アプリケーション層 BFF -
ソフトウェアアーキテクチャ Backend プレゼンテーション層 ドメイン層 インフラストラクチャ層 アプリケーション層 プレゼンテーション層 ドメイン層 インフラストラクチャ層 アプリケーション層
通常のWebアプリ プレゼンテーション層 ドメイン層 インフラストラクチャ層 アプリケーション層 BFF -
ソフトウェアアーキテクチャ Backend プレゼンテーション層 ドメイン層 インフラストラクチャ層 アプリケーション層 プレゼンテーション層 ドメイン層 インフラストラクチャ層 アプリケーション層
通常のWebアプリ プレゼンテーション層 ドメイン層 インフラストラクチャ層 アプリケーション層 BFF -
APIにおけるプレゼンテーション層の扱い APIではプレゼンテーション層が薄くなる • Viewとユーザのインタラクションはないため、そのあたりのハンドリング実装が不要 • APIのView = レスポンスのJSON(or XML) •
Java→Viewの変換はJacksonが担うため、⾃前で実装することはほぼない プレゼンテーション層はアプリケーション層に統合する形で設計 -
Backendのアプリケーション層実装例 @Usecase @AllArgsConstructor public class ReservationUsecase { /** * 予約一覧を返却する
*/ @Nonnull public GetReservationResponse getReservations(...) { //... // GetReservationResponse = レスポンス構造を表すレスポンスDTO(View) // Usecase(アプリケーション層)の時点でViewのインスタンスを返却する return GetReservationResponse.builder() .totalCount(count) .reservations(reservationDtos) .build(); } • アプリケーション層の時点でView(Json)と対応するDTOを返す -
Backendのアプリケーション層実装例 • ControllerはUsecaseの返り値をResponseEntityにラップして返すだけ @RestController @AllArgsConstructor public class ReservationController { private
final ReservationUsecase usecase; /** * 予約一覧を返却する */ @GetMapping public ResponseEntity<GetReservationResponse> getReservations(...) { final var body = usecase.getReservations(); return ResponseEntity.ok() .body(body); } -
ソフトウェアアーキテクチャ Backend プレゼンテーション層 ドメイン層 インフラストラクチャ層 アプリケーション層 プレゼンテーション層 ドメイン層 インフラストラクチャ層 アプリケーション層
通常のWebアプリ プレゼンテーション層 ドメイン層 インフラストラクチャ層 アプリケーション層 BFF -
BFFにドメインモデルは不要(だと思う) "ドメインモデル貧⾎症"になりやすい • BFFには業務ロジックが存在しないため データモデルとViewの間に作る中間表現としてのメリットが⼩さい • Backend層:リレーショナルデータモデル(データモデル)→Java(ドメインモデル)→JSON(View) • BFF層:JSON(データモデル)→Kotlin(ドメインモデル)→JSON(View)の変換 •
データモデルの表現⼒(型やデータ構造)が⾼い • データモデルとViewのインピーダンスミスマッチも⼩さい -
アプリケーション層 ドメイン層 BFFのアーキテクチャ Repository Repository Repository Service Service Backend API
リソース リソース リソース リソース リソース リソース 集約結果 集約結果 -
BFFのドメイン層実装例 • RepositoryはただAPIを呼んで返すだけ • JSON→Kotlin(DTO)間の変換はすべてJacksonが担う @Repository class HairSalonRepository(private val api:
HairSalonApi) { @Cacheable(cacheNames = ["publishing"], key = "'/hair-salons/' + #id") fun getById(id: String): Mono<HairSalon> = api.getHairSalon(id) } -
BFFのアプリケーション層実装例 fun getMessageBox(userId: String, salonId: String): Mono<HairMessageBox> = clientSalonRepository.getById(salonId) .flatMap
{ clientSalon -> salonRepository.getById(salonId) .map { it.toDto() } // サロンが掲載中なら掲載情報を使って返却する // 非掲載(掲載情報の取得に失敗した)ならサロンボードの登録情報を使って返却する .onErrorResume { Mono.just(clientSalon.toDto()) } } // メッセージの検索と、未読数の取得を行う .flatMap { messageRepository.getMessagesBySalonId(userId, salonId) } .map { (salon, response) -> HairMessageBox( total = response.total, salon = salon, messages = response.messages.map { it.toDto() } ) } • Serviceの実装例、複数のAPI(Repository)を呼び出して結果を集約していく -
アジェンダ 1. BFFとは何か? 2. BFF/Backend分割の⽬的 3. BFF/Backend分割の⽅針 4. BFF/Backendの設計・実装 •
API設計 • 技術選定 • ソフトウェア設計・実装 5. BFF/Backend構成の開発体制 6. まとめ -
現在のAPI開発体制 • アプリチームによるBFF層の開発体制構築はこれから • 現在は層分割を進めてきたメンバがAPIチームとして独⽴ • アプリチームと共にエンハンス案件実施を⾏っている モバイルアプリ 向けAPI
Webアプリ アプリチーム(8名) サーバサイドチーム(数⼗名) モバイルアプリ Backend API BFF API APIチーム -
BFFからBackendにかける負荷に変化があるときは、変更を共有する必要がある • 例えば集約するデータが増える(= Backend APIの呼び出しが増える)とき Backendチームによるレビュー体制が必要 • 新しいBackend APIを作る必要はないか? •
Backendサーバのスケールアップ/アウトは必要ないか? BFF開発体制構築の課題(2/2) Backendにかかる負荷を無視できない -
実は今の体制が最適? • 課題を踏まえると、今の体制が最適なのでは?という気持ちもある • BFF層の開発オーナーをAPIチームとして 「アプリチームがBFF APIにコミットする⽂化や仕組み」を作るほうが効果的では? • 模索中...
アプリチーム(8名) モバイルアプリ Backend API BFF API APIチーム Pull Request -
前提1:BFFの開発は、DBにかかる負荷を気にせず⾏えると嬉しい 前提2:BFFの開発で加わる変更の⼤半は参照系 • 表⽰要件に関するロジックだけをもたせているので • つまり参照負荷への対策が⾏うことが重要 前提3:BFF/Backend構成はSQLチューニングが難しい • BackendはREST APIを提供し、BFFはそれを様々な形で組み合わせる
• そのため、BFFからの使い⽅に合わせたSQLチューニングというのは難しい 余談:BFF/Backend構成はリードレプリカのスケールアウトがキモ? -
• こういった前提を踏まえると 「参照のためのDBリソースを増やす = リードレプリカのスケールアウト」 がBFF/Backend構成のキモなのではないか?と思える。 • ちなみに、ホットペッパービューティーではOracle
DBを利⽤しているため スケールアウトが難しい 😅(※ インスタンス課⾦のため) • Backendレスポンスをキャッシュするなど、別の負荷対策が重要になってくる 余談:BFF/Backend構成はリードレプリカのスケールアウトがキモ? -
アジェンダ 1. BFFとは何か? 2. BFF/Backend分割の⽬的 3. BFF/Backend分割の⽅針 4. BFF/Backendの設計・実装 •
API設計 • 技術選定 • ソフトウェア設計・実装 5. BFF/Backend構成の開発体制 6. まとめ