Timee Product Team Blog

タイミー開発者ブログ

タイミーのAndroidアプリ開発で採用しているモジュール分割の取り組みについて

はじめに

はじめまして、Androidエンジニアのsyam(@arus4869)です。

普段は愛知県からフルリモートで勤務していますが、最近は褪せ人として荒野を駆けています。

本記事の概略

本記事では、タイミーのAndroidプロジェクトで挑戦しているモジュール分割の取り組みについて紹介します。

また内容の理解を促すため、マルチモジュールについても軽くおさらいします。

本記事では、以下の方を読者として想定しています。

  • 他社のモジュール分割の手法に興味がある方
  • マルチモジュールなプロジェクトに挑戦してみたい方

現状のタイミーとアプリについて

タイミーのAndroidアプリでは、レイヤー単位でのモジュール分割はある程度行っていますが、機能単位でのモジュール(以降、Feature Moduleと呼びます)分割が行われていないのが現状です。

現在タイミーの開発組織は「マッチング領域」と「スポットワークシステム領域」という2つの領域でわかれています。今回は私が所属している「マッチング領域」の話です。

マッチング領域ではLeSSを採用しており、POと2つの開発チームが存在します。また、そのチームの枠を超えて技術的な課題を横断的に解決するため技術領域ごとにCommunityが存在します。現状マッチング領域のAndroidエンジニアは全員Android Communityに所属しており、Community内で話し合った方針に基づきアプリケーションのアーキテクチャの改修やリファクタリングを行っています。その中の一つがAndroidアプリのモジュール分割です。これを行うことで将来発生する開発人数・コード規模の増加などに伴って発生する様々な問題に対処しやすくなると考えています。

LeSSについて詳しく知りたい方はこちらのThe LeSS Company B.Vが提供しているイントロダクションが参考になると思います。

タイミーのアプリ開発におけるチーム構成

f:id:arus4869:20220311124857j:plain

課題

現状抱えている課題は大きく2つあります。

  • 開発組織の拡大に伴い、複数人での並列的な開発により生じる課題

    • 広範囲で強制化されていないコードが存在しているため、コードを触る開発者の人数が増えることによって、アーキテクチャの強制が効かなくなる可能性がある
    • コンフリクトの増加
    • 別開発チームのレビューにかかる時間が増加
  • サービスの拡大に伴いコード量が増加することで生じる課題

    • ビルド速度の低下
    • リファクタリングにおける影響範囲が読みづらくなる
    • コードが読みづらくなる

他にも様々な課題が考えられそうですが、モジュール分割を適切に行うことによって、上記課題の改善がされることを期待しています。

モジュール分割とは

タイミーのAndroidプロジェクトで採用しているモジュール分割の取り組みについてお話しする前にモジュール分割について簡単におさらいします。

Android StudioなどのIDEで新規プロジェクトを作成すると、最初にApplication Module(appディレクトリを持つモジュール)が作成されます。この状態はシングルモジュールなプロジェクトと呼ばれます。

f:id:arus4869:20220308234001p:plain

このApplication Moduleを複数のモジュールに分割することが可能であり、モジュール分割されたプロジェクトを「マルチモジュール」なプロジェクトと呼びます。 本記事における「マルチモジュール」なプロジェクトは、1つの「Application Module」と「Application Module」から分割された複数の「Library Module」から構成されます。

また「Library Module」からも複数の「Library Module」を分割することが可能です。

他にも「Dynamic Feature Module」などのモジュールがありますが、本記事では取り扱わないこととします。

モジュール分割の具体的な方法については、Doroid Kaigiのcodelabの既存のアプリをマルチモジュール化するを参照してみると良いと思います。

「マルチモジュール」なプロジェクトの例

f:id:arus4869:20220308234046j:plain

モジュール分割をする際は、既存の「Application Module」にあるレイヤーや機能を分割することで、思考の関心事を分離することができます。そのためモジュール分割を実施する前にどう分割するかを計画する必要があります。

またモジュール分割する際の注意点として、モジュール間の依存関係を単一方向に保つ必要があるため、抽象化の範囲を適切に考えておく必要があります。

モジュール分割を行なうために例えば以下のようなステップが考えられます。

分割のステップ

  1. コードの共通部品をモジュールに分割する
  2. 関心を切り離したいレイヤーを決めて分割の優先順位をつける
  3. 優先順位ごとにモジュールを分割する
  4. 古いUIのコードを別のパッケージに移す
  5. UIのコードを「Feature Module」として機能単位に分割

優先順位の付け方は諸説ありますが、ステップを踏んで適切にモジュール分割をすることによって、様々な恩恵を受けることができます。

モジュール分割のメリット

モジュール分割を行なうことで受けられる恩恵のひとつとしてビルド時間の短縮化が挙げられます。

既にビルドが行われ編集されなかったモジュールは、キャッシュ化されます。 そのため、ビルド時には編集されたモジュールだけをコンパイルすれば済むので、ビルド時間の短縮につながることいわれています。(Gradleによるビルドシステムの恩恵が大きいと考えられます)

モジュール分割では、ビルド時間の短縮だけでなく、以下のように様々なメリットが考えられます。

  • コードを触る範囲を狭め、ある程度強制することができる
  • 関心ごとを分離できるため、コードを触る範囲が明確になる
  • 関心ごとを分離できるため、機能の実装に集中できる
  • 関心ごとを分離できるため、新規開発者が参画しやすくなる

モジュール分割でコードの抽象化が行われることによる恩恵が大きいと思います。また、先述で挙げた課題についても改善に繋げることができると思います。

他にも様々なメリットがありますが、計画を立てずにモジュール分割を目的にするとモジュール分割に失敗し、逆にデメリットになる場合もあります。

モジュール分割のデメリット

モジュール間の依存が増えると、依存間の関係性が複雑になることによりビルド時間が伸びる可能性があります。 そのため、結局ビルド時間の短縮に繋がらなくなるので過度な抽象化には注意が必要です。

モジュール分割による抽象化は正解がないので、規模が大きくなるにつれ、計画を練る難易度が高くなります。 そのため小さくステップを踏んで、少しずつモジュール分割を進めていくのをお勧めします。

現在のアーキテクチャ

タイミーのAndroidアプリでは、1つの「Application Module」(以下、appモジュール)と20の「Library Module」で構成されています。

ここでは全体のモジュールの構成と代表的なモジュールの役割について簡単に紹介します。 全体のモジュールの構成としては、下図の通りです。

f:id:arus4869:20220308234132j:plain

基本的にはappモジュール(Application Module)がほとんどのモジュールと依存しています。appモジュールにはレガシーなコードがまだ混在しているので、新旧それぞれのコードを別パッケージに振り分け、新パッケージ内に内包されているUIのコードが分割したモジュールと依存するようになっています。

  • レガシーなUIのコードはappモジュール内のviewパッケージ
  • 新規に実装するUIのコードはappモジュール内のuiパッケージ

analytics

分析基盤のモジュールです。Firebaseの他にAdjustやReproなどのアナリティクスに特化したモジュールです。

pubsub

Publishersからメッセージを送り他の画面でSubscribeするモジュールです。

募集画面などでお気に入りしたイベントを他の画面に通知させたりしています。

Rxで仕組みを実現させています。

component

部品として独立したモジュールを内包しています。

  • imageviewer
  • license
  • pickimage

style

ActivityやUIコンポーネント等のstyleがAppThemeとして定義されているモジュールです。

core

Andoroidコンポーネントの開発で使用する共通的なプログラム等が内包されているモジュールです。

repository

ドメインモデルをリソースとして返すインターフェースが内包されているモジュールです

repository-impl

repositoryモジュールのインタフェースを実装したドメインモデルの具象を取得するプログラムが内包されているモジュールです。

各モジュールについて簡単に触れさせていただきましたが、また別の機会にて具体的に紹介できたらと思います。

タイミーのモジュール分割の現状ですが、先述の「分割のステップ4」にあたる「古いUIのコードを別のパッケージに移す」に位置しており、「分割のステップ5」の「UIのコードをFeature Moduleとして機能単位に分割する」ことを目指している最中です。

Feature Moduleによる機能分割について

本記事で取り扱う「Feature Module」についてですが、「機能としての画面」をモジュール単位で分割することを指しています。

例えば下図の「検索」や「募集一覧」が「機能としての画面」にあたります。

f:id:arus4869:20220309001632j:plain

このような「機能としての画面」を「Feature Module」で機能分割して利用する場合、下図のような構成になります。

f:id:arus4869:20220308234233j:plain

このように機能単位で分割することによって、自チームが検索機能を実装する場合、募集機能を実装している別チームに影響を与えずに同時に開発可能な領域を増やすことができます。

機能単位で独立して実装できるので、並列のチームを抱える開発組織において、モジュール分割でのメリットと同時に強力な恩恵を受けることができると考えています。

このような恩恵を受けるため「Feature Module」による機能分割を目指しています。

FeatureModuleにおける画面遷移について考えていること

「Feature Module」による機能分割を成功させるためには、画面導線を秩序立って配置する必要があります。そのため、画面の中に複数機能への遷移があった場合にモジュールを組み合わせしやすい形にしておきたいと考えています。

下図のような画面遷移が例として挙げられます。

f:id:arus4869:20220308234303j:plain

しかし、相互依存しないような画面遷移について考えなくてはならないため、どのように「Feature Module」を分割すれば良いか悪戦苦闘しています。

下図は相互依存するような画面遷移の例になります。(実際に下図のような画面遷移はしないのであくまで例です。)

f:id:arus4869:20220308234328j:plain

Gradle上でモジュールの参照を定義した場合、循環参照になってしまい相互依存してしまうため、単純に「Feature Module」同士での依存は、上図の画面遷移を考慮した場合に実現不可能です。

f:id:arus4869:20220308234350j:plain

相互依存しない画面遷移を実現するためには、「画面遷移の抽象化」を考えなければなりません。

現状においては、中間にモジュールを挟むことによって「画面遷移の抽象化」を図りたいと考えており、下図のような構造をイメージして具体の実装を考えています。

f:id:arus4869:20220310235704j:plain

「画面遷移の抽象化」についてはまだ道半ばなため、次回以降の記事で取り上げたいと思います。

おわりに

本記事では、タイミーのAndroidプロジェクトで採用しているモジュール分割の取り組みについて紹介させていただきました。

タイミーは挑戦できる土壌がある会社で、脳みそをたくさん使うことができます! モチベーションの高い仲間たちと様々なことにチャレンジしてみませんか? 是非興味をお持ちの方は気軽にタイミーへ応募してみてください!

iOS Communityの@sky_83325もマルチモジュールについての記事を書いてますので、こちらも興味があれば読んでいただけらと思います!

tech.timee.co.jp