Links

モバイルアプリのアーキテクチャ

モバイルアプリのアーキテクチャ

この章では、モバイルのアーキテクチャについてまとめています。 Wantedlyでのモバイルの歴史から、現在の状態、そして将来の展望について記述しています。
この章は、継続的にメンテナンスされ、モバイルエンジニアのオンボーディングの一助になることを期待します。

Why

モバイルアプリにおいて、アーキテクチャにバージョンが生まれるのは必然です。 既存のアーキテクチャの課題感への解決策として新しいバージョンのアーキテクチャが生まれたり、パラダイムシフトが起きるとアーキテクチャが変わっていきます。
アーキテクチャは、バージョンが新しくなるたびに改善していきます。 しかし、古くなったアーキテクチャをすべて一度にリニューアルするのは、膨大なコストが掛かるため非現実的であり、徐々に古いものを新しくしていくのが現実的です。 そのため、常に古いバージョンのアーキテクチャと向き合う必要があります。
この章は、古いアーキテクチャについては軽く、現在のアーキテクチャについてはより詳細にまとめ、今後のアーキテクチャの理想像を描いていきます。

Wantedlyのモバイルアプリ

現在、WantedlyではVisit, Intern, Peopleの3つのアプリを公開しています。

Visit

Visitは、ユーザーと会社が気軽にマッチングできるアプリです。
2014年から提供しています。 2018年のiOSリニューアル時にReact Nativeが導入されました。 2021年にReact Nativeは取り除かれ、KMMが導入されました。
Visit iOS
2018年にフルスクラッチでリニューアルしています。リニューアル後は、アーキテクチャパターンとしてReactorKitがほとんどの画面で採用されています。 2020年にプレミアムというアプリ内課金機能が追加されました。
Visit Android
iOSのフルスクラッチのリニューアルは、リソースの大量投入が起きてプロダクト開発が止まるという問題点があったため、Androidは2018年から段階的なリニューアルを開始しました。 リニューアル以降は、ReactorKitライクな記述ができるAndroidのViewModelを採用しています。

Intern

Internアプリは、学生の流入を目的としたアプリで、Visitと同じrepo・同じコードでできています。 そのため、Visitと全く同じアーキテクチャで同じ機能を持ちます。

People

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

現在のiOS/Androidのアーキテクチャ

アプリ・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
    • ドメインで取り扱うオブジェクト

Reactive Programming

データストリームを構築し、データに変更があったときに即座に変更を伝搬する仕組みです。 一覧画面と詳細画面でブックマークの状態を共有する、APIのレスポンスを即座にUIに反映するといった要件を満たすために使われます。
現在のモバイルのアーキテクチャにおいて、これは必須の概念です。 実装としては、iOSではRxSwiftとCombine、AndroidではRxJavaとLiveDataとKotlin Coroutines Flowがあります。 概念の理解と、どれか一つの実装を習得する必要があります。

Kotlin Multiplatform Mobile (KMM)

KMMは、2020年からVisitへ導入されています。 経緯とか説明は、ブログにまとめられているので、React Nativeをやめる話とKotlin Multiplatformを参照してください。

KMMの方針

KMMの導入は、Visitでは積極的に進める方針ですが、全社的に導入を進めるのは小さく検討している段階です。
具体的には、ログイン処理は全社的に共通のロジックであるため、wantedly/mobile-sharedというrepoでKMMの共通実装を行い、実験的にVisitとPeopleへ導入を進めています。

KMMのアーキテクチャ

Presentationレイヤーとして、ReactorKitライクな独自のアーキテクチャを採用しています。 採用理由としては、KMM導入の学習コストを減らすために、既存のアーキテクチャに似せたためです。
KMMアーキテクチャ
次項からより詳細に解説します。

Reactor

ReactorはReactorKitを元に作られた、単方向データフローを実現する独自のフレームワークです。
Reactorのデータフローは、図にすると次のようになります。
Reactor
  • UIからタップなどのトリガーでActionが送られる。
  • Actionmutate()によってAPI通信などの副作用を経てMutationに変換される。
  • Mutationreduce()によって現在の`Stateを変化させる。
  • UIはStateのストリームを監視してUIを変化させる。 といった単方向のデータフローを持ちます。
eventerrorは、ReactorKitにはない概念です。
eventはワンタイムなイベントが流れてくるストリームです。Reactor内からpublish()することでストリームにイベントが流れます。 例えば、登録フローのように登録API成功後に画面遷移を伴う場合、Reactorから完了のイベントを放出してUI側で画面遷移を行うときなどに使います。 段階的な導入をする上で、ナビゲーションは共通ロジックでは所持しない方針にしたため、UI側へイベントを投げて遷移させるためにこのようなストリームを作りました。
errorはその名の通りReactor内部でハンドリングされたエラーが流れてくるストリームです。Reactor内からerror()することでストリームにエラーが流れます。 通信エラーのようにハンドリング可能なエラーを放出して、UI側でエラーを表示するときなどに使います。
詳細な実装はWantedly VisitにおけるKotlin Multiplatformの導入と実装や実際のコードを参照してください。

DB as a Single source of truth

KMMのアーキテクチャでは、データベースをSingle source of truthとして使っています。 Single source of truthとは、データの操作などをすべて1箇所に集約することで、データの更新は即座に参照先へ伝わる仕組みです。
募集検索画面と詳細画面でブックマークの状態を共有する例で見ていきましょう。
依存の流れは次のようになっています。
依存の流れ
各画面は、最初にデータを取得します。データはDBのストリームを介して、常にProjectの最新の状態がUIまで通知されます。 破線は、データを購読(Observe)していることを表します。
データの流れ
ここで、詳細画面でブックマークをしたとします。 このときのデータフローは次のようになります。
ブックマークのフロー
DBへ状態の変更を保存することで、DBは最新状態をブロードキャストします。 ブロードキャストはストリームの購読者である2つの画面両方に通知され、結果UIまで即座にブックマークした状態が反映されます。

将来の展望

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