プロダクトの成長と組織の成果を最大化するために、バックエンドを移行した話
こんにちは。@hayata-yamamotoです。
本記事では、弊社が1年以上かけて取り組んできたバックエンド移行についてご紹介します。技術的な話は控えめに、「どんな課題意識を持っていたか」や「どんな風に進めたか」、「それによってどんな結果が得られのか」を中心に共有いたします。
スタートアップや新規事業など、スピード重視で開発してきたプロダクトをもう一段成長させるために移行プロジェクトを計画されているEMやCTOのみなさん、彼らに提案したいと思っている優秀なエンジニアメンバーの皆さんにとって参考となりましたら幸いです。
結果について
要点をまとめると以下のようになります。
結果を定性面、定量面の2軸で整理すると以下のようになります。
側面 | 結果 |
---|---|
定量面 | 1. バックエンドシステムのカバレッジが 0% → 80% に増加 2. QA時の不具合検出率が、前年の同時期に比べて 1/10 に低下 |
定性面 | 1. ローカル開発時にテストやデバッグが柔軟に行えるようになった 2. 自分たちの好きなタイミングでログを発行したり、エラー通知を行えるようになった 3. フロントエンドとバックエンドの責任分界点が OpenAPI定義で規定されるようになった |
どのような課題があったのか
以前、弊社のバックエンドは以下のようなアーキテクチャを採用していました。主に AppSync がフロントエンドとの接点となり、内部的に Lambda を呼び出したり独自のロジックを実行したりしていました。Terraform, Serverless Framework の両方でアプリケーションコードが提供されている状況でした。なお、GraphQL の Subscription処理は提供していませんでした。
現在は以下のような状態となっています。Lambda 関数上では FastAPI を起動しなるべく一つの Lambda 関数でアプリケーションが稼働するような構成をとっています。[1] APIGateway が AppSync に変わってフロントエンドとの接点となっています。全て Serverless Framework で構築する Lambda アプリケーションで完結しています。
『プロダクトに貢献したい』と『怖くて触れない』のジレンマを解消したかった
プロダクトにとって重要な機能を開発する際には多くの場合、バックエンドの開発が必要になってきます。データの永続化が必要になったり、複雑なロジックを提供しなくてはいけなかったりと、プロダクトの成長や多様な顧客のニーズに応じて、バックエンドシステムは必然的に成長が求められていきます。
しかし、移行前のバックエンドはユーザーの期待や要望に十分に応えていける状態ではありませんでした。理由はさまざまありますが、代表的なものを挙げると以下のようになります。
メンバーのやる気が高く、コミットメントもある状況であっても、提供・運用している仕組みや設計によってメンバーが本来発揮できるパフォーマンスの上限が決まってしまうのは看過できません。
もちろん、技術的な意思決定の過程で、いわゆる”技術負債”を意図的に創出することはありますし、必ずしも全ての仕組みを完璧にする必要はないと考えています。しかし、チームの状況やプロダクトのフェーズ、顧客のニーズに追従できない仕組みを運用し続けることは、チーム全体の士気を下げてしまいますし、最終的にユーザーが得られる便益の量も減少してしまいます。
また、当時からリリース時には品質保証のプロセスを設定していましたが、開発段階で十分にデバックしきれずQAに持っていくこともしばしばあり、不具合対応により別のタスクのスケジュールに影響が出るなど、プロダクト開発全体で見た時にも課題が表出する状況を生み出していました。
今後プロダクトをより大きく、大胆に成長・拡大していくことを見据えて組織やチーム、そしてプロダクトのありようがどのようになっていなければならないかを検討した際に、より運用保守がしやすく、開発メンバーが自発的にコミットしやすい体制や環境を整えることが重要であると考え、バックエンドシステムの移行を実施することにしました。
移行がフロントエンドにも恩恵をもたらすように
また、本移行に合わせてフロントエンドも事業やプロダクトの成長によりコミットでき、本質的に重要な作業にフォーカスできるよう以下の点を特に注意し、バックエンドの移行を進めていきました。
現在では、新しい機能を開発する際には以下のような流れをとることにより、フロントエンドとバックエンドの開発がある程度分離できる状態となりました。
現在、フロントエンドでは ReactQuery と aspida[2] を組み合わせてAPIとの疎通を行っており、openapi2aspida[3] を用いて OpenAPI定義から TypeScript の型定義を自動生成しています。これにより、バックエンドで定義された example データを使用する、もしくは faker で生成されるモックデータを用いてフロントエンドに閉じた開発ができる状態となりました。現在、フロントエンドではこれらの恩恵を活かし、Storybook, Chromatic を用いたフロントエンドテストに注力して取り組んでいます。
なぜそのように考えたのか?
以前は、検証中の実装やちょっとしたロジックをフロントエンドに記述していくスタイルを多く採用していました。これは、サーバーサイドの開発しにくさ、不具合が出てしまう恐れを懸念し “触らぬ神に祟りなし” な状況が発生していたためです。メンバーは状況を理解した上開発してくださっていたものの、どうしてもフロントエンドの開発ボリュームが大きくなる割にはクライアント都合で挙動がうまく制御できなかったり、複雑なロジックをクライアント側で実装しないといけない状況が生み出されていました。
結果的に、プロダクトの進捗にフロントエンドのメンバーがコミットしてくれており、成果を出してくれているのにも関わらず、変更をしていないバックエンドに比べて不具合の発生件数は高くなりがちで、あまりフェアなプロダクト開発ができている認識ではありませんでした。本来、バックエンドがやるべき事項をフロントエンド側で肩代わりしたのにさらに、そのロジックについても対応が必要になるというあまり健全な状態ではありませんでした。本来考えるべき、ユーザーのインタラクションやUI上の見せ方、操作感などに時間が取れず、必要な機能は提供できるがやはりクオリティ面にコミットしきれていない状況を生み出していました。
どのように解決したのか
今回の移行では、「具体的にいつまでにこの取り組みを終わらせるか」は明確に定めずに実行しました。念の為ですが、意図して期限は決めていません。なるべく、機能開発の優先順位を下げたくなかったのに加え、達成すべきスケジュールが複数になってくるとどうしても注意が分散してしまい、本来チームとして出せるパフォーマンスが出なくなってしまうことを懸念したためです。
とはいえ、バックエンドのリプレイスを実行すると決めた後、さまざまな取り組みを実行しました。代表的なもので言うと以下のようになります。
コードフリーズ期間を設定した
移行に際してコードフリーズの期間を設けました。タイムラインを可視化すると以下のようなイメージになります。最初に、小さなコードフリーズ期間を設けて、3割程度移行を完了させたのち、新規機能の開発と並行して残りの移行を進めました。
このコードフリーズ期間は、主に品質面での不安に対応するために実施したものですが、結果としてはとても良かったと考えています。理由はいくつかあります。
特に重要だったと思うのは、3の「できる」とチームが思い込めるというところでした。私個人としても、実際にやってみないとわからないから「本当にできるだろうか」と懸念する事項もあったのですが、どんなに小さなスプリントでもやることによって見えるものがあり、事象に対する解像度が高まります。
短いコードフリーズ期間ののち、開発自体は通常の開発に戻るのですが、移行する際の取り決めや土台についてはある程度合意形成が済んでいることもあり、チームの中で「今月はこのAPIは終わらせておきたいね」とか「このAPIもう実装終わってるなら置き換えやってしまおう」など相談・議論ができる状態を作れました。
新しい機能を実装する際には、既存のAPIを拡張するのではなく新規にAPIを作成すると取り決めた
たかが取り決めですが、されど取り決めです。この取り組みも意義がありました。今回の移行では、フロントエンドが享受するメリットも含まれており、実際に新規のAPIを用いて開発した方が従来のAPIを用いて開発するよりも、開発体験がよくスムーズに進む事例がいくつか続き、最終的には「なるべく新しい機能を使った方がユーザーに良い体験を提供できるし、早く済む」とチームで認識できるようになっていきました。
一見すると、2兎を追っているようで難しい問題を同時に解いている状況のように見えますが、全体のコストを考えると弊社の場合は同時に解いてしまう方がスムーズに開発が進みました。プロダクトの機能や仕様を十分に知らないメンバーと一緒に、新しいAPIの設計をしながら既存仕様も共有することで、開発したAPIの利用イメージが双方の中に生まれたというのが影響していると考えています。
新規でAPIを全て作成した後、バックエンドのメンバーがフロントエンドの置き換えを行った
原則、新旧両方のAPIが本番環境に共存する状況を維持しながら、機能単位で切り替えを行なっていく戦略で移行を進めていました。その過程で、どうしても問題になってくるのは「旧APIの利用が残ってしまい、置き換えが完了しない」という問題でした。フロントエンドを担当しているメンバーも移行を全て対応する余裕が十分にあるわけではなく、このまま放置してしまうと状況的に、APIの置き換えが一向に進まない懸念がありました。
結果、弊社ではAPIを移行したメンバーでフロントエンドコードも置き換える対応を進める意思決定を行い、フロントエンドの新規開発と衝突しないように気をつけながら改修を行っていきました。実際に繋ぎ込んでみると思ったように動かなくて修正が必要であったり、リクエストに想定していた値をフロントエンドで取得するのが大変だったりと、提供しているAPIが実際にどのように使われているかをチームで把握するのにかなり有益でした。
Discussion