エンジニアの藤村です。
私の所属するチームでは先日、写真共有・販売という機能の設定画面をリニューアルしました。
リニューアルは機能の利用開始に至るまでの体験について改善を目指したものでした。実装の土台にある画面の情報構造から変更する内容だったことに加え、元々技術的負債からの脱却に取り組んでいる背景もあり既存の基盤の上で改修を行うのではなくこの機会に新基盤でReactに書き直す方針を取りました。
個人的にプロダクトの保守性を高めることへの興味が強いこともあり、新基盤の保守性は自分の守備範囲にするんだという気持ちで取り組みました🔥
本記事では、リニューアルに取り組む中で保守性を高めるため試行錯誤した過程を振り返ります。
こちらはコドモンAdvent Calendar 23日目の記事です。
スタート地点
私たちのチームにはバックエンドに強みのあるエンジニアが多く、フロントエンドを書き直す選択は今回大きな挑戦となりました。どの単位でコンポーネントに分割するか、スタイルの当て方はどんな方針を取るのがいいかなど基本的なところからのスタートでした。
遭遇した課題
ロジックが複雑化するコンポーネント
開発の初期段階では、UI構造の再利用を目的とした分割を行う中でコンポーネントの持つロジックが複雑化する課題がありました。
たとえばテーブルの表示について「サイズ」というカラムをAの箇所では表示し、Bの箇所では非表示にしたいといった場合に、コンポーネントの引数を増やしたり内部のロジックに分岐を増やして個別に対応していたのです。利用箇所が増えるごとに分岐が複雑になってしまう状況でした。
この問題の原因はコンポーネントの責務を分離できていないことにあると考えました。今回の事例ではUI構造の再利用がコンポーネント分割の目的でしたが、カラムの表示/非表示の制御についてまで責務を負っていました。
対応としてPresentational/Containerパターンのように、UI構造を再利用できる箱を提供するコンポーネントと箱にどんな値を渡すかを制御するコンポーネントで分けて考えるようにしました。 これにより切り出したコンポーネントではUI構造の抽象化だけに集中することができ、再利用性を高めることができました。
ライブラリとの密結合
我々のチームでは、OpenAPIのAPI仕様書からレスポンスの型を自動生成する仕組みが整っています。API仕様書が変更された時にコードがそれに追従していないとビルドに失敗するようになっており、仕様書が常に最新化されていることを担保できるという素敵な仕組みになっています。 この仕組みによってOpenAPIで定義したレスポンスの形に対応した型がフロントエンドとバックエンドそれぞれに生成されます。
初めはこの自動生成されたコードにアプリケーション全体がべったりと依存してしまっていました。 それによって、APIに変更を加える際にフロントエンドのUI構造の末端の階層にいるようなコンポーネントまで影響が波及してしまうことがありました。
アプリケーション全体がべったりと依存してしまっていました
と書いたのですが、具体的には各コンポーネントのインターフェースにOpenAPIによって自動生成された型が頻繁に登場していました。
type HogeProps = { setting: ResponseSetting // APIの定義に基づいて自動生成された型 } export const HogeComponent = (props: HogeProps) => { return (....) }
本来コンポーネントがどんな情報を必要としているかという話とレスポンスの構造は分けて考えられるものだと思うのですが、そのような設計にできていませんでした。
そこで、以下のような方針を持って対応を行いました。
- 各コンポーネントは自動生成された型に依存するのではなく、自分が必要な情報にだけ依存すること
type HogeProps = { size: string type: string amount: number } export const HogeComponent = (props: HogeProps) => { return (....) }
- APIのレスポンスを受ける箇所で、自前で定義した型に詰め替える処理を入れる(自動生成された型を以降の層で登場させない)
type HogeDTO = { hoge: string; fuga: number; }; const fetchHoge = (): Promise<HogeDTO> => { return fetch("/api/hoge") .then((response) => return response.json()) .then((data) => { hoge: data.hoge, fuga: data.fuga }) };
これで、API変更した際に影響を受ける箇所を限定的にすることができました。変更の多いものに依存すると修正も高頻度で必要になってしまうことを身をもって体験しました。
どこを疎結合にしてどこは結合を許すのかの感覚が難しいですが、実践を重ねながら、例えば今回の例では「バックエンドとフロントエンドの結合部分は疎結合にできると良い」というような言語化に取り組み続けられるとそこの感覚が身につきそうだなと感じました。
フロントエンドの事情を知りすぎているバックエンド
今回BFFを用意する方針の中で、フロントエンドの状態管理の初期状態をどうするかというところまでBFFで扱い、レスポンスに反映していました。
// フロントエンドで活性・非活性どちらに振り分けるかを意識しているレスポンス { enableList: [....], disableList: [....] }
その結果、フロントエンドの状態管理で扱うオブジェクトの構造を変えたくなったときにバックエンドのロジックまで改修が必要になりました。
こちらの対応は時間の問題でリリースに含められなかったのですがチームで課題を認知し、将来どうできると嬉しいかという認識を揃えることができました。工数と優先度、メリットを比較して今後良いタイミングで改善できればと考えています。
チームでは、バックエンドがフロントエンドの事情にまで関心を持つのをやめ、フロントエンドの事情はフロントエンドで完結できると良いという結論になっています。
// フロントエンドの事情を意識しないレスポンス // hoge > 10ならenableとするなどの判断はフロントエンドで行う { list: [ {hoge: 20}, {hoge: 4} ] }
学んだこと
良い境目を見定め、適度に関心を分離する
今回出会った課題はおおむね、関心の分離によって改善する内容でした。 関心の分離という話題については、クリーンアーキテクチャやドメイン駆動設計を読み、少し分かったつもりでいました。
しかし、いざそれを実践してみるとなかなかうまくいかず、気付かないうちに関心事の境目を間違えてしまうことがありました。 例えば「フロントエンドの事情を知りすぎているバックエンド」に挙げた例では、バックエンドの責務とフロントエンドの責務の境目が少しずれ、本来フロントエンドで持ちたい責務をバックエンドで持ってしまったのですが、それに開発終盤まで気付けずにいました。
小さな修正が広範囲に影響するという症状からフロントエンドの関心事がバックエンドに漏れているのに気付けたことは学んだことを発揮できて偉かったなと満足しつつ、今後はどこに境目を引くかの感覚を身につけられるともっと手前の段階で改善できるようになるのだろうなと伸びしろを感じました。
今後は理論を実践に落とし込むことにどんどん挑戦していきたいです🔥
テストを仕様書化する
今回取り組んで良かったこととして、テストを仕様書化する意識を持ったことが挙げられます。
頻繁に自動テストが実行される場合、テストは常に最新化される仕様書として扱うことができると言われています。 それを意識して実際に仕様書として扱えるようなテストを書いてみると、モジュールの持つ責務が複雑になっていないかが分かるメリットを感じました。

仕様書として扱うなら前提知識のない読み手にも伝わるような簡潔な一言でモジュールの振る舞いを表現したいところですが、モジュールが責務を抱えすぎている場合それが難しくなってきます。
また純粋に仕様を把握しやすくなるため、未来の開発や調査をスムーズにすることにも期待して今後も続けたい取り組みです。
自分の感じた課題を言語化してチームに共有し、みんなで向き合う
今回の開発を通して、技術的な面だけでなくソフトスキルの面でも学びがありました。
初めは自分の頭で思っていることを言葉にしてチームに共有していくことに苦戦しました。 最初に課題を感じたときはPR上のコードやコメントで言いたいことを表現してみたのですが、差分が巨大で情報量の暴力になってしまったなと反省しました。
感じた課題の言語化に力を入れることでチームへの共有を完結に行うことができ、かつ自分にとっても学びをより深くすることができました。
上記の反省から、次に感じた課題ではいきなりHowの話を始めるのではなく、どこに課題を感じたかのWhatの部分を投げかけるように改めたところ、議論が弾み、自分の感じた課題をチームの課題に広げることができました。


おわりに
今回の記事では、写真共有・販売機能の設定画面リニューアルの中で保守性の高い設計を目指した過程を振り返りました。
技術的な学びはもちろん、ソフトスキルの面でも学びを得ることのできた経験でした。 引き続き目の前のことに全力で向き合い、来年のアドカレで今年よりも成長した姿をお見せできればと思います!
最後まで読んでいただきありがとうございました!