プロダクトエンジニアリング部の小林です。
2022年4月に新卒として入社し、アプリケーションエンジニアとしてバックエンドからフロントエンドまで幅広く開発を行っています。
この記事では、配属されたチームで取り組んだリファクタリングをどのように行ったか、その舞台裏について紹介します。
プロジェクトの技術選定と背景
技術選定
配属されたチームでは新規のtoC向けプロダクトの開発が始まっていました。 技術仕様は以下のとおりです。
アーキテクチャ: Clean Architecture
フレームワーク: Next.js x TypeScript
アプリケーション実行基盤: 内製ライブラリ「KEEL」
Clean Architecture x Next.js x TypeScript
LIFULLでは3年前にリアーキテクティングプロジェクトが発足し、ソフトウェアアーキテクチャのベースにClean Architecture、言語にTypeScriptを採用し、新たなAPI(Backend For Frontend)を開発してきました。
新規開発でも基本的に同じ思想に基づいて設計を行います。
しかし今回はLIFULL HOME'Sの中心である物件情報を提供するシステムをリアーキテクティングする開発ではありません。
さらにモノレポで開発を行いたい事情があったため、Next.jsのAPI Routeで構築できるAPIをBackend For Frontendとして利用することにしました。
社内で先行してClean Architecture x TypeScriptでモノレポ開発をしているチームがあるため、多分に参考としています。
アプリケーション実行基盤: 内製ライブラリ「KEEL」
全社で利用している内製ライブラリ「KEEL」を利用しています。開発者がアプリケーション開発に集中できる素晴らしい基盤です。
詳細は下記の記事から御覧ください。
www.lifull.blog
開発からリリースまで
チームではClean Architecture x TypeScriptなBFFの開発は初めてでした。
チャレンジングな開発としてアーキテクトグループによるレクチャーを受けながら動き出しており、配属から3ヵ月で最初のリリースが予定されていました。
私は開発経験こそあるものの、機械学習やゲーム開発がメインだったため、Web系の言語は入社してから触れています。
TypeScriptとClean Architecture、どちらも初めての勉強の毎日でしたが、エンドポイントの一部を開発する中で理解を深めることができました。
1つのエンドポイントを層と機能で分割することで開発タスクを分割できるのもClean Architectureの良いところですね。
さて、無事にリリースを迎え一安心かと思いきや…
私はリリース直前となったころ、大きな問題に気付きます。
「このプロダクト、Clean Architectureになっていない…!?」
理想の状態
Clean Architectureは層で役割を分け、依存関係を内向きにすることで外部に依存しない状態を理想とするものです。 Next.jsや外のAPIに依存するのは下の図で言うところの緑の層までにとどめ、ビジネスロジックは外部に依存しないように設計することで、APIやDB、フレームワークの仕様変更への対処が簡便になります。 なおかつドメイン知識を集約したクラスを作り、値オブジェクトとして利用することで入ってくる値をバリデーションする役割も担います。
リリース時の状態
一方、勉強しながら手を動かして実装していく中でアーキテクチャへの理解を深めることができ、その結果認識したプロダクトの実態は以下の通りです。
- 層ごとにファイルは分かれている
- ドメイン知識が集約されておらず、GatewayやUseCaseに分散している
- Next.jsの型に強く依存している
(Next.jsのRequest型をGatewayまでトンネルしている) - 社内APIに強く依存している
(社内APIからのレスポンスをkeyとprimitiveな値をそのままにPresenterまでトンネルしている)
つまり、ファイル数が多く複雑であるのに外部に強く依存したClean Architectureとは呼べないシステムになっていました。
簡単に言えば現状の社内APIを使うことを前提としたコードになっていて、いつの日か
- 社内APIが新しいものに置き換わったとき
- フロントを司るNext.jsが開発終了・破壊的変更をしたとき
にこのプロダクトは7割方のコードを書き換える必要がある作りになっていました。
リファクタ開始
プロダクトの状態に気付いたものの、動作は正常であり、リリース予定日が目の前であったためそのままリリースすることになりました。
その前提で上長に状態と対策案を伝え、リファクタに工数を割くことにGOサインをもらいました。
リファクタの方針と準備
まずリファクタの方針と優先順位を定めました。
- 社内APIに対する依存を切る
- Next.jsとの依存を切る
- ドメインクラスを育成する基盤を作る
その次に準備を行い、チームやアーキテクトグループの協力を仰ぎました。
- アーキテクトグループとの相談
- 作業内容の切り分け、順序の設定、チケット化
- チケットのレビュー (影響範囲を小さくしながら細かい粒度でアジャイル的に進めるため、確認は必須と考えました。)
- リファクタ実行
リファクタの手順
- 社内APIへの依存を切る (レスポンスを型変換する層を作る)
- APIからの戻り値に型をつけたInterfaceを作る
- RepositoryにUseCase層で使う値のInterfaceを作る
- APIから受け取った値をRepositoryに詰め直す処理を作る
このとき、詰め替え元の方として1で作った型を使い、詰替え先の方として2で作成した型を使う
- Next.jsへの依存を切る
- ドメインを固める
以上の手順でリファクタを行いました。 私が学びながらの開発でアーキテクチャの理解が進んだからと言っても開発の経験は少ないので、まずはより良い状態を目指すためにアーキテクトグループの方にモブプログラミングをしてもらいました。 モブプログラミングの中でClean Architectureに頻出のTypeScriptの技法やリファクタ技法を学びました。
社内APIにアクセスするInteractorからGatewayまでの流れの一つをモブプログラミングとしてリファクタしたうえで、その学びを元に残りのコードを自らリファクタしました。
またリファクタと並行して
- ユニットテストの導入・API統合テストの導入
- OpenAPI / Swaggerの実装
- ESLintの設定の最適化
を行い、プロダクトの健全化を図りました。全体を通して開発者の心理的安全性がかなり高まったと感じています。
まとめ
今回、リファクタをすることで、Clean Architectureに関する知識を深めることができ、TypeScriptの型に対する理解が深まりました。 またリファクタで得た問題点と解決策や知見をチームに共有することで、並行していた開発が進んでいた新機能の品質も向上しました。
Clean Architectureに限らず、大規模なリファクタを行う際には
- 知見のあるスペシャリストに事前相談する
- チームに随時情報共有を行い、負債を作らないチームを目指す
- 自分の理解度が低い場合にはモブプログラミングも有効
ということを学べたことが最大の成果です。
今後も高品質で開発者体験の良いプロダクトを育てながら、ユーザーに価値を届ける基盤としていきたいと考えています。
最後に、LIFULL ではともに成長していける仲間を募集しています。よろしければこちらのページもご覧ください。