コドモン Product Team Blog

株式会社コドモンの開発チームで運営しているブログです。エンジニアやPdMメンバーが、プロダクトや技術やチームについて発信します!

サービス成長の痛みに向き合う、技術的負債解消の取り組み

こんにちは、登降園・請求管理機能*1チーム所属の髙橋です。 コドモンではソフトウェアの品質を向上させて、価値提供のスピードをあげるために技術的負債の解消を進めています。

note.com

独立性が高い機能については、順次リプレイスして技術的負債の解消と陳腐化した技術の刷新を進めていますが、複雑度が高くコードベースが大きい機能については、様々な制約からすぐにリプレイスに着手することができません。

そこで登降園・請求管理機能チームにおいては、課題を整理しつつ段階的にリプレイスに向かう方法を取ろうとしています。 この記事は昨年1年間で集中的に取り組んだ請求管理機能の技術的負債解消の取り組みについて説明しています。

請求管理機能の状況

請求管理機能は登降園の打刻時間や利用した食事の実績を元にして、保護者への請求を作成する機能です。

ビジネスルールが複雑で計算のパターンも多いためコードベースも大きく、また機能追加が断続的に繰り返されたことで複雑度が高まりメンテナンスコストが増大しています。

テストスイートはバランスが悪く、膨大な手動テストと部分的なE2Eテスト・インテグレーションテストによって品質保証が行われており、アイスクリームコーン型アンチパターン *2 に近い構成となってしまっていました。

アイスクリームコーン型アンチパターン
アイスクリームコーン型アンチパターン

またデータベースのテーブル設計にも問題があり、長期的な保守を考えると是正が必要な状況です。

リプレイスに向けた作戦

一旦モジュラモノリスを目指す

前述の通りコードの複雑度が高く、またチーム発足時はビジネスドメインに対する理解が十分とはいえない状態 *3、かつ仕様的な面でも一旦整理が必要で、このような状況でリプレイスに踏み切るのはリスクが高いと考えていました。

そのため、一旦は既存のリポジトリの中でリファクタリングを行ってドメイン間の依存関係を整理し、また将来的にマイクロサービスとして切り出す可能性も見据えてモジュラモノリスを目指すこととしました。

このようにすることでビジネスロジックを新たに実装し直すことができ、なおかつ同時並行で既存機能のメンテナンスも行えると考えたからです。フレームワークや実行環境については既存の資産を活用することができるため、コードの問題に集中できるというメリットもあります。

チーム内での知見の共有、議論の促進

チーム発足時はドメインの知識も設計への理解も不足している状態でした。そこでモブプログラミングやペアプログラミングを増やして、設計についての共通認識を作りつつ、議論を促進して、よりよい最適な設計に向かえるようにしました。 またこれよりも前の段階で、先々を見据えてドメイン図の作成を実施していたことが設計や議論の役に立ちました。

設計

Clean Architecture+ドメイン駆動設計

アーキテクチャとしてはClean Architectureとドメイン駆動設計を採用しました。ビジネスロジックが複雑でメンテナンスも長期間に渡るため、変化に強く可読性が高い設計としたかったことが理由です。 先行するリプレイスプロジェクトで採用されていたため、社内の経験者から助言を受けつつ進められるというのも理由の一つでした。

モジュラモノリス

一旦機能ごとのまとまりをドメインの境界*4とみなし、ドメインの境界を超える場合は各ドメインが提供する公開APIを通すルールとしました。 こうすることで各ドメインの独立性が高まり、また分散していたコードも徐々に集約されていきます。

モジュラーモノリスと公開APIの概念図

腐敗防止層

既存のコードから取得した情報は腐敗防止層で変換するか、プロキシ的なクラスを置くことでリファクタ後のロジックが影響を受けないように配慮しました。DBについてもアンチパターンを踏んでいるものは将来的に是正が必要になるため、インターフェース層にTranslatorと呼んでいる独立した変換クラスを配置する形にしました。

テストのバランス是正

ユニットテスト>インテグレーションテスト>E2Eテスト というバランスになるように徐々に是正することを目指します。実行速度が早いユニットテストをもっとも厚く作成して、ここでカバーできない内容をインテグレーションテストで保証するような方針としました。 実装においてはTDDを導入してテストファーストを基本方針としましたが、ユニットテストについてはカバレッジ100%を目指さず、モックによる効果の薄いテストに時間をかけるよりはインテグレーションテストやE2Eテストで保証する方針としました。

途中経過

一旦既存リポジトリの中でリファクタリングを実施することで、比較的少ない工数で機能改善しやすい状況を作ることができました。

新たに書き直したドメイン層のビジネスロジックはテストによる品質保証が可能な状態になり、可読性も変更容易性も向上しました。コードの品質向上はその後の不具合修正で証明がされました。 また、この先リプレイスして開発言語が変わってもドメイン層のビジネスロジックはそのまま使用できるため、今後の資産となります。将来につながる資産を実装しているという実感がエンジニアのモチベーションにもつながったように思います。

モブプロ、ペアプロを推進したことで設計やドメイン知識の共有が進み、業務の中で設計や進め方に関する議論が頻繁に行われる状況が作られました。

自動テストは拡充を進めていますが、まだまだ不十分で手動テストが多く残ってしまっています*5。自動テストの拡充とバランスの是正が今後の課題です。

最後に

取り組みを始めた頃は十分な知見もなく不確実性の高い手探り状態でしたが、チーム内外からの助言と議論を通して徐々に正しい方向に向かえるようになってきたと思います。 すべての問題を一度に解決するのは困難で、解決策についても十分な知見があるとは限らないため、正しい方向に向かえるよう議論を絶やさず、徐々に軌道修正するのがよさそうに思えます。現在のところはこのやり方が上手くいっているように感じます。

ソフトウェア開発とはチームによる取り組みであるため *6、知識を共有し議論を促進することで、誤りを正してよりよい方向に向かえると考えています。

*1:登降園管理機能は園児の登園・降園時刻の予定と実績を管理する機能です。請求管理機能との関係が深いため現在は一つのチームで担当しています。請求管理機能については本文をご覧ください

*2:手動テストとE2Eテストで品質保証の大部分が行われ、インテグレーションテストやユニットテストはほとんど書かれない。速度が遅く信頼性も低くなるテストのアンチパターン

*3:ユーザー数の急増に対応するためエンジニアの採用を進めており、それに伴いビジネスドメインに詳しいエンジニア層が相対的に少なくなってしまっていました

*4:一つのモデルを共有できる境界。境界づけられたコンテキスト。エリック・エヴァンスのドメイン駆動設計から

*5:正直に言えば工数と期限の問題でユニットテストは十分に作成することができませんでした...やむを得ない状況とはいえ若干後悔が残りました

*6:Googleのソフトウェアエンジニアリング 2章から「本章において決定的に重要な考え方は、ソフトウェア開発はチームによる取り組みであるということだ。」