モバイルアプリのアーキテクチャ
この章では、モバイルのアーキテクチャについてまとめています。 Wantedlyでのモバイル の歴史から、現在の状態、そして将来の展望について記述しています。
この章は、継続的にメンテナンスされ、モバイルエンジニアのオンボーディングの一助になることを期待します。
モバイルアプリにおいて、アーキテクチャにバージョンが生まれるのは必然です。 既存のアーキテクチャの課題感への解決策として新しいバージョンのアーキテクチャが生まれたり、パラダイムシフトが起きるとアーキテクチャが変わっていきます。
アーキテクチャは、バージョンが新しくなるたびに改善していきます。 しかし、古くなったアーキテクチャをすべて一度にリニューアルするのは、膨大なコストが掛かるため非現実的であり、徐々に古いものを新しくしていくのが現実的です。 そのため、常に古いバージョンのアーキテクチャと向き合う必要があります。
この章は、古いアーキテクチャについては軽く、現在のアーキテクチャについてはより詳細にまとめ、今後のアーキテクチャの理想像を描いていきます。
現在、WantedlyではVisit, Intern, Peopleの3つのアプリを公開しています。
Visitは、ユーザーと会社が気軽にマッチングできるアプリです。
2014年から提供しています。 2018年のiOSリニューアル時にReact Nativeが導入されました。 2021年にReact Nativeは取り除かれ、KMMが導入されました。
Visit iOS
2018年にフルスクラッチでリニューアルしています。リニューアル後は、アーキテクチャパターンとしてReactorKitがほとんどの画面で採用されています。 2020年にプレミアムというアプリ内課金機能が追加されました。
Visit Android
iOSのフルスクラッチのリニューアルは、リソースの大量投入が起きてプロダクト開発が止まるという問題点があったため、Androidは2018年から段階的なリニューアルを開始しました。 リニューアル以降は、ReactorKitライクな記述ができるAndroidのViewModelを採用しています。
Internアプリは、学生の流入を目的としたアプリで、Visitと同じrepo・同じコードでできています。 そのため、Visitと全く同じアーキテクチャで同じ機能を持ちます。
2016年から提供しています。 つながりの情報をローカルに保持し、各OSの連絡先に同期する機能があります。このために一部にRealmが使われています。 名刺撮影機能のためにOpenCVが使われています。 両OSどちらもMVVMアーキテクチャを採用しています。
細かな点は省きますが、各アプリ・各OSのアーキテクチャの移り変わりは、次のようになっています。
アプリ | OS | バージョン | 言語 | アーキテクチャパターン | UI実装 |
---|---|---|---|---|---|
Visit | iOS | 1 | Swift | Flux(ReactorKit) | UIKit(コード) |
Visit | iOS | 2 | Swift | Flux(KMM Reactor) | UIKit(コード) |
Visit | Android | 1 | Java | MVC | XML |
Visit | Android | 2 | Kotlin | Flux(ReactorKitライクViewModel) | XML |
Visit | Android | 3 | Kotlin | Flux(KMM Reactor) | XML |
People | iOS | 1 | Swift | MVVM | UIKit(コード) |
People | iOS | 2 | Swift | MVVM | UIKit(xib) |
People | Android | 1 | Java | MVC | XML |
People | Android | 2 | Kotlin | MVVM(Rx) | XML |
People | Android | 3 | Kotlin | MVVM(Coroutines) | XML |
アプリ・OSによって細かい部分が異なっています。 現状、アーキテクチャパターン(MVVMやFluxなど)は、全体では統一されていません。
大枠では、次のような共通点があります。
- ある程度Clean Architectureに従った、3~5層のレイヤードアーキテクチャ
- Reactive Programming(Rx、LiveData、Coroutines Flow)によるUI状態管理
Visitの一部で導入されているKMMについては後述します。
大原則として、レイヤーの依存方向は必ず上から下になります。下層が上層の参照を持ってはいけません。 例:ViewModelがViewやViewControllerへの参照を持つ。
Use Caseが存在しない場合もありますが、概ね現行のアーキテクチャのレイヤー構成は、次のようになっています。

現行のアーキテクチャ
- UI
- Frontend Behaviorの状態を元にUIを構築したり、画面遷移といったユーザーに見えるものに責務を持つレイヤー
- iOSならViewControllerやView、将来的にはSwiftUIが該当
- Presentation
- UIの状態を管理したり、UIからイベントを受けてビジネスロジックを呼び出すレイヤー
- ReactorやViewModelが該当
- Use Case
- Repositoryを隠蔽するレイヤー
- 基本的にPresentationから見た行動1つに1つのUse Caseが存在し、大体はRepositoryのメソッドと1:1
- Repository
- Data Sourceを隠蔽し、適切なData SourceからEntityを取り出したり、操作したりするレイヤー
- 基本的にEntityとRepositoryは1:1
- Data Source
- APIやDBなど、Entityを取得するレイヤー
- Entity
- ドメインで取り扱うオブジェクト
データストリームを構築し、データに変更があったときに即座に変更を伝搬する仕組みです。 一覧画面と詳細画面でブックマークの状 態を共有する、APIのレスポンスを即座にUIに反映するといった要件を満たすために使われます。
現在のモバイルのアーキテクチャにおいて、これは必須の概念です。 実装としては、iOSではRxSwiftとCombine、AndroidではRxJavaとLiveDataとKotlin Coroutines Flowがあります。 概念の理解と、どれか一つの実装を習得する必要があります。
KMMの導入は、Visitでは積極的に進める方針ですが、全社的に導入を進めるのは小さく検討している段階です。
Presentationレイヤーとして、ReactorKitライクな独自のアーキテクチャを採用しています。 採用理由としては、KMM導入の学習コストを減らすために、既存のアーキテクチャに似せたためです。

KMM アーキテクチャ
次項からより詳細に解説します。
Reactorのデータフローは、図にすると次のようになります。

Reactor
- UIからタップなどのトリガーで
Action
が送られる。 Action
はmutate()
によってAPI通信などの副作用を経てMutation
に変換される。Mutation
はreduce()
によって現在の`Stateを変化させる。- UIは
State
のストリームを監視してUIを変化させる。 といった単方向のデータフローを持ちます。
event
とerror
は、ReactorKitにはない概念です。event
はワンタイムなイベントが流れてくるストリームです。Reactor内からpublish()
することでストリームにイベントが流れます。 例えば、登録フローのように登録API成功後に画面遷移を伴う場合、Reactorから完了のイベントを放出してUI側で画面遷移を行うときなどに使います。 段階的な導入をする上で、ナビゲーションは共通ロジックでは所持しない方針にしたため、UI側へイベントを投げて遷移させるためにこのようなストリームを作りました。error
はその名の通りReactor内部でハンドリングされたエラーが流れてくるストリームです。Reactor内からerror()
することでストリームにエラーが流れます。 通信エラーのようにハンドリング可能なエラーを放出して、UI側でエラーを表示するときなどに使います。KMMのアーキテクチャでは、データベースをSingle source of truthとして使っています。 Single source of truthとは、データの操作などをすべて1箇所に集約することで、データの更新は即座に参照先へ伝わる仕組みです。
募集検索画面と詳細画面でブックマークの状態を共有する例で見ていきましょう。
依存の流れは次のようになっています。

依存の流れ
各画面は、最初にデータを取得します。データはDBのストリームを介して、常にProjectの最新の状態がUIまで通知されます。 破線は、データを購読(Observe )していることを表します。

データの流れ
ここで、詳細画面でブックマークをしたとします。 このときのデータフローは次のようになります。

ブックマークのフロー
DBへ状態の変更を保存することで、DBは最新状態をブロードキャストします。 ブロードキャストはストリームの購読者である2つの画面両方に通知され、結果UIまで即座にブックマークした状態が反映されます。
Visit iOSのリニューアルの経験から、フ ルスクラッチでのリニューアルというのは当面ないと想定されます。(チームやプロダクトとの合意があれば別です。) そのため、段階的に1画面ずつ変わっていくことが求められ、画面単位で依存性が別れたアーキテクチャが必要になってきます。
今後、Declarative UIというパラダイムシフトが確実に起きるため、アーキテクチャにさらにバージョンが増えることが予想されます。 アーキテクチャは頻繁に大きく変えられません。なので細かくアーキテクチャを改善し、古いバージョンのアーキテクチャは徐々に消し去っていきましょう。
話を聞きに行きたい
もっと知りたい