こんにちは、システム部のマルです。
最近、田舎に引っ越して自然に囲まれてコロナで疲れた心を癒しつつあります。
今日は既存システムをReact を使ってリニューアルしたので、振り返ってみたいと思います。
はじめに
今回リニューアルしたのは、外部企業にも提供しているアンケート配信サービスの一部です。
このアンケート配信サービスはマイクロサービス化していて、個々の機能が各々リポジトリに分かれています。
今回はそのうちの一つ、モノリシックなCakePHP で構成されているサービスをリニューアルしました。
既存の構成はCakePHP でview を描画し、バックエンドから適宜DB に直接アクセスしたり、他サーバーに設置されたAPI群を呼び出したりしていました。
これを今回以下のように、フロントエンドからAPI を呼び出す形にリニューアルしました。
- フロントエンド: React + Redux
- バックエンド: CakePHP
- 他リポジトリのAPI群: 変わらず利用
フロントエンドとバックエンドは、同じサーバーに設置し、他リポジトリのAPI群はCakePHP のバックエンドをラッパーとして呼び出すようにしました。
理由としては、以下のような点になります。
- 既存のフロントエンドが非常に複雑なため、Redux によるstate管理ですっきりさせたい
- デザインが古くさく感じられるようになってきているので、デザイン一新に併せて、フロントエンドもモダン化し技術的負債化を防ぐ
- 社内にモダンなフロントエンドの知見をためておきたい
API設計の方針
最初は細かくREST っぽくAPI を分けていたのですが、どうしてもAPI を呼ぶ回数が多くなり、オーバーヘッドも発生してしまいます。
速度的な観点から、なるべくフロントエンドのトリガー単位でAPI を設計する方針に途中で変更しました。
今回は比較的早い段階で、この方針に切り替えたのであまり無駄な作業は発生しませんでしたが、REST 寄りにするか、画面仕様に合わせるかは判断が難しい部分だと思います。
そのAPI がどれだけ再利用される可能性があるかによって、方針を決める必要があると思います。
また、トランザクションの管理など複雑なこともでてくるので、この方針は設計最初期の段階できちんと決める必要があるでしょう。
困難だった点
ここでは、実際にプロジェクトを進めるにあたって困難に感じたポイントをまとめていきます。
基本的な問題
まず仕様を把握する上で、以下のような問題点がありました。
- 既存の仕様をきちんと把握している人が社内にほとんどいない
- 既存システム自体についてはドキュメントがない
- フロントエンドでのデータ加工、表示/非表示のコントロールがかなり複雑に行われていて、ドキュメント化が困難
- バックエンドから呼び出しているAPI に関しては、一部ドキュメントがあるものの、実態に即していない箇所もあった
- バックエンドから呼び出しているAPI の言語がJava で、メンバーはPHP メインの人が多かった
- DB に過去データの画面描画用のjson を保存していて、それが既存のjQuery仕様を想定したものとなっている
僕に至っては、そのシステム自体をほとんど触ったこともない状態、かつVue の実務経験しかなくReact は実務では初めてだったのですが、設計の担当だったため、既存仕様の把握と画面項目の洗い出し・フロントエンドのおおまかな設計・API詳細設計などを1ヶ月ほどで行わないといけないような状態でした。
もちろん他リポジトリのAPI群もほとんど見たことがない状態でした。
jQuery とReact では考え方が根本的に違うので、仕様を把握していてさえ移行には気を使うことがたくさんあります。
その上、既存システムではjQuery をフル活用して、DOM にデータをattribute として描画しまくり、それをドラッグ&ドロップと連携させていたり、プルダウンの状態とログインユーザーの種類、過去データの状態などによる表示制御が複雑に絡み合っていました。
僕がまず最初にやったのは、Redux のstate に落とし込むために、全ての画面項目とそれによりトリガーされるイベントを書き出すことです。
そして、そのイベントの中身で変化する値をすべて仮想のstate として書き出しました。
その後、仮想state のデータ変化を「▷」「▶︎」などの記号で可視化し、どのイベントでどのstate を参照、変更しているかを把握できる状態にしました。
Redux で管理しているglobal state の構成が変わると後々やっかいなことになりそうだったので、ここでいったん全体像をある程度反映した叩き台を作ることは必須でした。
この時点で完全にstate を決めることはできませんでしたが、必要になりそうなstate をいったんおおまかに洗い出したことにより、object の構造をどのようにすれば後々大きな修正をせずに済むかをある程度把握でき、このおかげで、後々大きなstate の変更に伴う修正は発生しませんでした。
データ整合性の問題
state の概要設計をする上で、DB保存されている過去データのjson の問題が出てきました。
当初は、せっかくReact を使うのだから、データ保存時にstate をそのままjson で保存すれば、画面描画時もそのjson をそのままstate にセットすればいいかな、くらいに考えていたのですが、既存の過去データの変換は避けて通れません。
また、リスクヘッジのため既存システムをリニューアル後も稼働させておくという話もあったので、結局既存のjson のフォーマットに合わせざるを得ませんでした。
state をまるっと保存・復帰できるのはReact の利点だと思っていたので、これを実現できなかったのは、少し残念でした。
しかし、よく考えると、またReact から他のものへ移行する日がいつか来るので、結局json の保存フォーマットを画面実装に依存させるのは避けた方が良いでしょう。
マイクロサービスアーキテクチャの弊害問題
さらにバックエンドとのインターフェース定義を作成する段階で、以下のような問題が発生します。
- 既存の仕様はかなり大きいjson オブジェクトをバックエンドに渡しており、バックエンドだけ読んでもjson の構造が把握できない
- さらに他リポジトリ(複数かつJava)のAPI の中身まで把握しないと、実際にどういう構造のjson が渡され、どういう処理が行われているのか把握できない
- React の知識がないメンバーには仮想state を可視化した資料ではデータの流れを把握できない
- バックエンドのみしか実装しないメンバーは既存のフロントエンドのコードを読んでいない
つまりバックエンド側からは単純にAPI パラメータのインターフェースは把握できても、その中身のjson がどういう構造かまでは簡単には把握できない状況だったわけです。
しかし、バックエンド側は単純に自分に渡されたパラメータを他リポジトリのAPI に引き渡すだけなので、そこまで把握する必要がありません。
結局、パラメータオブジェクトを作成するフロントエンド側でjson オブジェクトの構造を把握するしかない状態でした。
今回のプロジェクトで僕の担当はフロントエンド開発メインだったのですが、結果として、バックエンド・他リポジトリのAPI のソースまである程度把握しなければ、フロントエンドの詳細な設計が難しいという状況でした。
これはAPI がマイクロサービス化して様々な言語で乱立していると起こりうる問題のひとつだと思います。
もちろんメリットもたくさんあるのですが、ドキュメントが充実していないと、仕様の把握が難しくなるというデメリットの一つに今回直面することになりました。
言語の壁問題
また、言語の壁を感じる場面もありました。
今回は、ほぼフルリモートでの作業かつ英語のみしか話せない新卒メンバーがバックエンドのメイン実装者という状況でしたが、チームの中には英語での読み書きが不得意なメンバーもいます。
今後、英語オンリーのメンバーと共同で開発するような組織も増えてくると思うので、こういった状況に直面することもあるでしょう。
ただ、日本語英語両方でドキュメントを作成するとなると、更新・管理の手間が2倍になり無駄が生じてしまいます。
できれば英語のみに統一すべきだとは思いますが、現実問題なかなか難しいので、ある程度言語の壁によるコミュニケーション量の増加を見越した上でスケジュールを立てるべきかもしれないと感じました。
さらに、React 含めたJSフレームワーク自体未経験のメンバーにReact を教えるというタスクもありました。
ここまで整理すると、以下のようなチャレンジングな状態を乗り越えなければなりませんでした。
- 既存仕様が全くわからない状態からのスタート
- 既存仕様のjQuery が複雑で可視化が困難
- フロントエンドの仕様を把握していないメンバーによるバックエンド設計の補足・修正
- 依存API の中身把握に時間がかかる
- 日本語がわからない開発者向けに英語での補足説明・質問対応
- 自分もReact 自体は実務はじめて
- jsフレームワーク未経験メンバーにReact を教える
- フルリモートでコミュニケーションがとりづらい
フロントエンド開発に集中できない問題
また、今回ログイン部分と他リポジトリAPI リクエストクラスの土台に関しては、バックエンドの実装も担当していたため、その部分を急いで実装する必要がありました。
ログイン部分は、サービス共通のフレームワークリポジトリを利用してSSO 化されており、共通仕様にする必要がありました。
この実装に伴って、API リクエストのベースクラス、Csrf 対策、React への繋ぎ込み部分などベースとなる部分を全て実装し、ようやくフロントエンドの開発にはいったのですが、ベースとなる部分を僕が作ったために、開発初期にコード周りでの質問が僕に集中するという自体が発生しました。
ここに関しては、React への繋ぎ込み部分以外はバックエンド側のメンバーに任せるべきだったと後悔しています。
反省点
そんなこんなで非常にチャレンジングなプロジェクトで、結果的には約1ヶ月ほどスケジュールが遅れ、バグもいくつか発生してしまいました。
反省点ですが、まず最初に設計の段階でもう少し役割を分散してもらうべきでした。
今回フロントエンド側の負担が非常に大きかったにも関わらず、既存のフロントエンドのソースを読んでいるのが僕一人という状態で、社内にはそこのソースについて把握している人が一人もいなかったので、相談できる相手が一人もいませんでした。
途中からもう一人のフロントエンドメンバーとプロジェクトリーダーにもソースを読んでもらうようお願いしたのですが、最初の段階で、バックエンド側のメンバーにも既存のフロントエンドのソースを読んでもらうようお願いし、既存仕様について相談できるメンバーをつくるべきでした。
また、開発初期、フロントエンドはモックサーバーを立ててモックデータを用いた開発を行っていたのですが、実際にバックエンドと結合する際に、インターフェースの型不整合などが発生し、少し余計な時間がかかってしまいました。
これはOpenAPI などを使ってモックデータを作り、データの不整合が起きえない状態を作るべきでした。
依存API については、マイクロサービス的なアーキテクチャを採用している場合、今回のような問題は避けがたいです。
ドキュメントをきちんと更新することを意識することは重要ですが、ドキュメントと実際のコードがずれることはどうしても発生してしまうと思います。
やはり、OpenAPI のようなツールを導入して、インターフェースの乖離を防ぐことが重要になると思います。
まとめ
非常に反省点の多いプロジェクトでした。
コロナ禍ということもあり、フルリモートで開発を行う際の大変な面も経験できました。
今後、フルリモートが当たり前になっていくと予想されている中で、初期のうちにこのようなチャレンジングなプロジェクトを経験でき、とても良かったと感じています。
少し残念なのは、一緒に頑張ったプロジェクトメンバーとプロジェクト後の食事や飲み会に行けないことです。
早く気軽にみんなで飲みに行けるようになるといいですね!