こんにちは、エンジニアの taroodr (中村)です。
3ヶ月ほど前アウトドア用品の中古品買取と販売を行うhinataリユースの新規開発が立ち上がり、私が大枠の技術選定をすることとなりました。 www.hinatareuse.jp
結論
はじめに結論を書いておきます。
最初はマイクロサービスの設計パターンの1つである BFF(Backend For Frontend) を導入する構成で開発を進めました。
しかし、ある問題が発生し最終的にはバックエンドとしては1つのAPIサーバのみの構成に移行しています。
この記事ではなぜ上記のような結論に至ったのか背景と意思決定の理由を時系列に沿ってお話しします。
アーキテクチャの全体像
最初に考えたアーキテクチャの全体像は図のような形です。
この図の黄色の枠線で囲んだ部分が新規で開発する部分です。
現在kubernetes上にすでにいくつかのマイクロサービスが存在しており、
それらを新規開発するプロダクトからも使う可能性が高いということで、マイクロサービスを集約するBFF層を導入するアーキテクチャを選定しました。
補足しておくと、全体的な構成として、プロダクトごとにBFFを配置するよう設計されており、 それにしたがってリユースでもBFFを配置し、設計上のイレギュラーとならない(設計観点での複雑性を高めない)ようにした点も導入理由の1つです。
次にそれぞれの領域でどんな技術を採用したのかをお話しします。
frontend / Next.js / TypeScript
他のプロダクトでの導入実績があり、社内の知見が豊富といった観点でNext.jsの採用を決定しました。
作るプロダクトの特性として全ページ認証されたユーザが利用することが前提でありSSR・ISRといった機能を使うことは想定されないため、
ピュアなReactのアプリに移行するという選択肢も取れるようになっています。
BFF / NestJS / GraphQL
社内の他のプロダクトでは、BFFにGo + gqlgenを採用しています。
弊社ではフロントエンドを書くエンジニア、バックエンドを書くエンジニアが分かれており
1つの機能を作る場合でも最低2人のエンジニアが関わる場合がほとんどです。
BFFにGoを採用している場合、必然的にバックエンド側のエンジニアがBFFを書くことが多くなります。
その結果発生したのが本来BFFに書くべきだったデータの整形ロジックがフロントエンドに流出してしまうという問題です。
この問題を解決するためにフロントエンドエンジニアでも触りやすいTypeScriptベースのフレームワークNestJSを使うことにしました。
NestJSを採用した実績はなかったので、社内向けの勉強会を開催するといった取り組みも行っています。 github.com
↓BFFを書く言語に何を採用するか相談しているslack
microservice / Go / gRPC
- すでにGoで書かれたマイクロサービスがいくつか動いており実績がある
- 今回新規で開発するプロダクトも将来的に他のプロダクトから参照される可能性が高い
といった観点から既存のマイクロサービスと同様にGo + gRPCという技術選定にしました。
新規開発にBFF + マイクロサービスを採用したことによる問題
上記の構成を採用し2ヶ月弱ほど開発を進めたところで、以下の問題を顕著に感じられるようになってきました。
- 考えるべきことが増え、設計の難易度が上がる
- 書くコード量が増える
- 開発速度が下がり、機能をデリバリする速度が下がる
以上を背景として、 新規開発においてBFF + マイクロサービスは必要であるかを検討するミーティングを開催することになります。
補足
弊社ではプロダクトごとに開発チームを組んでいて、1つのチーム(2~4人)が複数マイクロサービスを取り扱う組織構成になっています。
チームの人数が多く、1チームが1マイクロサービスを扱うようになれば別のメリットが発生するため上記は問題にならない場合が多いはずです。
新規開発においてBFF + マイクロサービスは必要であるか
ミーティングの議事録の一部を掲載します。
reuseにおけるBFF削除のメリット・デメリットを羅列する ### メリット - デリバリ速度(リリースの頻度)があがる - 1つの機能をリリースするまでのスピードが半日~1日程度あがる - 考えることが減る - スキーマ設計 - デプロイの順番 - 認可の仕組みをどこでいれるか? など - 単純に書くコード量が減る - NestJSを覚える必要がなくなる - 表示速度があがる(BFFを経由する必要がないので) ### デメリット - gRPCサーバが消えることによりAPIを他のプロダクトから呼びずらくなる(今のところ予定はないが) - 他のプロダクトから呼び出す方法 - GraphQL APIを他のサービスから呼び出す - access tokenがないと叩けないAPIばかりなので、他のプロダクトからアクセスがあるときはそれように新しいAPIを作らないとダメかも? - BFFがTypeScriptなのでフロントエンド畑の人も触りやすいといったメリットが消える - 結果フロントエンドにロジックが寄りがちになるかも - あとで分割する際に地獄をみるかも? - 他のプロダクトと構成が変わる - webサーバとDBとのやりとりなどがまとまってて負荷大きそう?
このメリット・デメリットをもとに話し合った結論が以下です。
- プロダクト開発初期から他のサービス、プロダクトと通信する可能性があるのであればBFF+ マイクロサービスをこのまま採用しても良い
- 通信する可能性がないのであれば、BFF + マイクロサービスをやめて1つのAPIサーバに集約するという選択肢もあり
今回開発しているプロダクトにおいては、少なくとも半年から1年間は他のサービスとの通信がないので1つのAPIサーバに集約するほうがメリットが大きいと判断し移行を決断しました。
BFF + マイクロサービスからの移行
一時的に機能開発を止めて、gRPC サーバをGraphQLサーバに置き換える対応をおこないました。
置き換え後の構成図
幸い、レイヤードアーキテクチャでgRPCサーバを作っていたため、変更箇所は少なく1週間程度で作業は完了しリリースまで無事に終了しています。
シンプルな構成に移行してみて変わった点
- 1つの機能をつくりあげるまでの時間が短くなる
- 設計に関して考えるべきことが減る
特に新規開発のようなある程度スピードが求められる場面ではこのメリットが大きいので、 今のところ削除という判断は正解だったと思っています。
今後に向けて
要件によって最適なアーキテクチャは変わるのは大前提として、弊社としては今後以下のような方針で構成を決めていきます。
- 開発初期でスピードが大事になってくる場合はシンプルな構成でつくろう
- プロダクト初期段階からマイクロサービスに分割しない方針を推奨
- 例外として、既存のマイクロサービスがありそれらを開発初期から使うような場合は最初に選択していたBFF + マイクロサービスパターンという選択肢も検討
We Are Hiring
vivitでは常により良いアーキテクチャを模索し、サービスを成長させていけるような仲間を募集しています。