Backend For Frontend(BFF)入門
初めまして、株式会社Gizumoでエンジニアをしているasaiです。
弊社の長期研修プログラムを経てエンジニアとなったのち、現在は客先常駐で様々なプロジェクトに関わらせて頂いております。
今回はBFF(Backend For Frontend)について扱っていきます。フロントエンドが担当することになる領域なので、ぜひ知識として知っておきましょう。
目次
前提・対象とする読者
この記事の読者は以下のような方を想定しています。
- BFFに興味がある方
- ドメイン駆動設計の経験がある方
- マイクロサービスを検討している方
- モノリシックなサービス開発で辛い経験をしたことがある方
- フロントエンドでWebやネイティブアプリなど複数のUI管理が必要な方
マイクロサービスとは
さて、今回はBFFを紹介したいのですが、その前にマイクロサービスを知っておく必要があります。
マイクロサービスとは、1つのアプリケーションをビジネス機能に沿った複数の小さいサービスの集合体として構成するアーキテクチャの1種です。
事業が拡大し複数のサービスを運用している企業などで採用されていることが多いです。
要件としては以下を満たしているものを指すとされています。
<要件>
- Highly maintainable and testable
メンテナンス性とテスト性に優れている - Loosely coupled
疎結合である - Independently deployable
独立してデプロイが可能である - Organized around business capabilities
業務的な能力を軸に整理されている - Owned by a small team
小さなチームにより所有されている
<簡単な定義>
ThoughtWorks社のマーチン・ファウラーとジェームス・ルイスが提唱したソフトウェアアーキテクチャで、モノリシック(一枚岩)なアーキテクチャとは異なり、ビジネス機能に沿った複数の小さい「マイクロサービス」に分割し、連携させることで大きいソフトウェア機能を実現することです。これにより、迅速な開発、実行、および、頻繁な機能強化、優れた可用性やスケーラビリティを実現しやすくなります。
最初にこのような定義を目にした際、おそらく多くの人が結局どういうことなのかイメージしにくいのではないでしょうか。
それもそのはずで、マイクロサービスの前段にはドメイン駆動設計が存在しており、ドメイン駆動設計の概念が分かっていないとマイクロサービスも正確には理解が難しいためです。
なお、ドメイン駆動設計を飛ばしていきなりマイクロサービスを構築することも出来ますが、多くの場合スケールする段階で多くの課題が出てくるので注意しましょう。
さて、簡単に言えばドメイン駆動で登場した境界づけられたコンテキストを単位として、サービスとして切り出されたものの集合をマイクロサービスと呼びます。
このあたりも「境界づけられたコンテキスト」がわかっていないとイメージしにくいですね。
もっと簡単にイメージするため、今から電卓を作ることにしましょう。
必要な機能は、足し算をする機能と引き算をする機能、掛け算をする機能と割り算をする機能とします。
これくらいなら簡単ですね。
バックエンド側にロジックを持たせることを想定し、足し算と引き算、掛け算と割り算を行うAPIを用意して、計算結果を値として返すとしましょう。
一方、フロント側では電卓のボタンをクリックした際に、バックエンド側で用意されたAPIを叩けるようにしておけばOKです。
このくらいなら話は単純なのですが、サービスを運用していくと、ユーザーからどんどん新しい要望が来て、機能追加が必要になってきます。
今度は、足し算をした結果を記録する、みたいな機能を追加するとします。
これもまだなんとかなりそうです。DBに保存してあげましょう。
さらに進むと、足し算をした結果を使ってユーザー同士でごにゃごにゃ…みたいなことがしたい、となってきます。
これもまだ大丈夫そう…ですが、これをn回繰り返した時、とんでもないことになります。
このような問題があり、現在は後々のことを考えて、マイクロサービスというアーキテクチャが採用されることが多くなっています。
イメージ的には足し算と引き算、掛け算と割り算を行う機能をそれぞれ独立して切り出して開発するという手法です。
それぞれが独立しているということは、足し算には足し算のデータストアがあり、引き算には引き算のデータストアがあり…みたいなイメージです。
めちゃくちゃ冗長に思えますが、これは作った当時はそこまでの規模感がなく無駄に思えても、後々のサービス拡大時に大きく有効に働いていきます。
以上が簡単なイメージです。
マイクロサービス導入時のフロントエンドの葛藤について
さて、実際にマイクロサービスを導入した場合を考えていきます。
万能に思えるマイクロサービスアーキテクチャですが、銀の弾丸ではありません。「全てのシステムの課題をマイクロサービスで解決できるわけではない」という意識で臨む必要があります。
小さすぎる組織がマイクロサービスを志すと、少人数で複数のシステムを管理することになるので開発や運用のコストが増加し、モノリシックなシステムには無い技術的・組織的な課題を抱えることもつながっていきます。
ここで、例えばサービスA=足し算の機能としましょう。
ユーザーからの要望により、足し算の機能(API)だけを管理していたサービスAは様々な機能追加が行われていきます。
DB連携機能、ユーザー間共有機能、バトル機能、実績機能、jpeg化機能、SNS連携機能、育成機能…
確かに、はじめは「足し算のサービス」として切り出し、開発を進めてきたはずです。しかしながら、足し算のサービスに機能が際限なく追加されていったことで、足し算のサービスが肥大化し、責務が増加していきます。
その結果、足し算のサービスは足し算のサービスと呼べる状況ではなくなっていきます。
こうなってくると、もう単純な機能追加ではなく、「一度切り出したサービスからさらに別のサービスを切り出す」必要が出てくるわけです。
これはバックエンド側でなんとかすればいいですが、問題はフロント側です。フロントエンド側ではサービス1つにつき、web、スマホ版web、スマホアプリ、デスクトップアプリなど複数のクライアントに対して、それぞれに足し算や新機能のAPIをサービスごとに叩き、メッセージやコンテンツの出し分けを実装する必要があり、結果的にクライアント側のコードが複雑化し、冗長なコードを書かざるをえない状態に陥ります。
なのでフロント側からすると「一度切り出したサービスからさらに別のサービスを切り出す」なんてとんでもない!やめてくれ!となるわけです。
Backend For Frontendの登場
サービスの数に比例して複雑化するAPIに対し、フロント側の対策としてBFFと呼ばれるアーキテクチャがあります。
意味としてはその名前のとおり、「フロントエンドのためのバックエンド」となります。
何をしてくれるかというと、フロントエンドのリクエストに応じて各種のAPIコールをしたり、バックエンドから取得した内容を加工してフロントエンドに返却したりしてくれます。
BFFの存在により、下図のようにフロントエンドはBFFにのみリクエストを送る事になり、通信量の肥大化を防ぐ事ができます。クライアントの実装も接続先はBFFのみとなるのでシンプルにできます。また、バックエンドAPIに修正があった際もBFFにてその対応が吸収できるので、各クライアントでの対応は不要です
このような背景があり、現在ではBFFと呼ばれる設計手法が採用されてきています。
BFFでよく採用される技術
①Nest.js
BFFを採用する際に利用されることが多いフレームワークの1つになります。
フロントエンド側でバックエンドを管理する際にはNode.js(Express)を用いることが一般的ですが、Node.jsには定まったアーキテクチャが存在しないと言う問題があり、そのためNode.jsを使っている人次第で、ディレクトリ構成やロジックの置き場所が異なってしまうという問題がありました。
このような問題を解決するべく、登場したフレームワークになります。
以下のような特徴があります。
- typescriptをデフォルトサポートしつつVanillaJSでも書ける
- expressやfastifyなどをサポートしている
- アーキテクチャがしっかりしているので、Node.jsの実装者による開発手法のバラ付きが起きにくい。
- 拡張性が高い
- テストしやすい
- OOP(オブジェクト指向プログラミング)、FP(関数型プログラミング)、FRP(関数型リアクティブプログラミング)の要素を兼ね備えている
このフレームワークを用いることにより、簡単にWeb APIサーバを用意することが可能となります。
しかも、言語がjavascriptなので、フロント側とバック側のコミュニケーションが容易になります。
記法はAngularに似ており、さまざまな機能をデコレータで付与するような設計です。
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
→CatsControllerクラスはコントローラであることをNest.jsに伝えてくれるのが@Controllerの部分です。
()の中に引数を指定してあげることでurlパス名を指定することができるため、この例で言うとlocalhost:3000/catsにアクセスすると値が返ってくるようになります。
@Get()以下ではGetリクエストを処理する際のロジックなどを記述します。今回だとThis action returns all catsというstring型の文字列が返ってくる形になります。
②Prisma
Nest.jsでWeb APIサーバを構築する際に、選択肢の1つとして上がることが多いORMになります。
ORMとは何かと言うと、データベースとモデルを関連づけて様々なデータ操作を可能にしてくれるツールで、イメージ的にはSQLでクエリを直接書かなくても、ORMの文法にしたがって書けばうまい具合に処理してくれる、みたいな感じで良いと思います。
LaravelでいうとEloquentなどでしょうか。
prisma studioを使えばDB内のテーブルをエクセルのように管理することも可能です。
JS系では他にTypeORMなども有名です。
③AWS AppSync
サーバレスでBFFを構築しようとした時に必要になる技術です。AWS AppSyncではエンドポイントの提供や認証認可、ロギングなどを提供してくれるので、クライアントとバックエンドの中間に入り込み、上手い具合にコミュニケーションしてくれます。
ただ、申し訳ないのですがこちらの技術を使ったBFFの構築はまだ経験したことがないため、多くを語ることができません。
興味のある方はAppSyncを使ったアーキテクチャについて調べてみて下さい。
終わりに
フロントエンドの担当領域が大きくなってきていると言われてから大分時間が経ってきました。
本来ビジネスロジックなどはバックエンド側で担保すべきですが、特にバックエンドやフロントエンドの責任範囲が明確でない現場では、否応なしにフロント側の比重が重たくなっていきます(そのためか、昨今ではマイクロフロントエンドという分野も登場してきています)。
フロントエンドをやっていると特にDB周りの知識だったりバックエンドの知識だったりが不足しがちですが、実務では求められることがあるので積極的に学習していきましょう!