2022/04/21 (木) に開催されたKyash Tech Talk #3の登壇資料です。
Mobileアプリのアーキテクチャ設計法〜KMMを例に〜Keita Kagurazaka
View Slide
課題と制約
課題と制約● 現状と理想のギャップが課題● 選択肢を制限するものが制約○ 技術的なもの○ チーム由来のもの○ 歴史的経緯によるもの● 制約を守りつつ、課題を解決しなければならない● アーキテクチャは課題の解決方法の1つ
Kyashでの課題
Kyashでの課題● [課題1] 両OSで同じロジックを2度実装することによる問題○ リソースの無駄遣い○ 同メンバーが対応することによる虚無感 (特にテスト)● [課題2] OSでロジックが異なったことによるインシデント○ 画面遷移可能かの判断で非常に複雑な場所があった○ その判定ロジックがOS間で異なり、片方のOSでだけインシデントが発生
制約条件● [制約1] 各OSごとにUIを最適化し、UXを最大化する方針● [制約2] フルリニューアルを実施することができない○ 事業の状態を考えたときのリソースに起因○ すなわち、部分的な導入がしやすく、それによって既存部分に与える影響を最小化したい
制約条件● [制約1] 各OSごとにUIを最適化し、UXを最大化する方針● [制約2] フルリニューアルを実施することができない○ 事業の状態を考えたときのリソースに起因○ すなわち、部分的な導入がしやすく、それによって既存部分に与える影響を最小化したい[選択1] Kotlin Multiplatform Mobile
共通化するレイヤー● KMMは部分導入の自由度が高い○ APIクライアントだけ○ UseCaseまで○ etc.
共通化するレイヤー● KMMは部分導入の自由度が高い○ APIクライアントだけ○ UseCaseまで○ etc.● [課題1] 両OSで同じロジックを2度実装することによる問題 の解決のためには[選択2] UI以外は原則すべて共通化する
選択による課題と制約
KMM採用の課題と制約● [制約3] iOSメンバーはKotlinの経験がなく、これから学ぶ必要あり● [制約4] 過去のPRにしかないストック情報がある○ [選択3] KMMのリポジトリを分ける○ アプリ側のリポジトリとのインテグレーションが必要○ [課題3] インテグレーションのオーバーヘッド● [課題4] Kotlin Nativeにおけるmulti-thread問題○ 以下native-mt問題
native-mt問題● Kotlin Nativeでは原則としてオブジェクトがスレッドを跨げない● 例外はfreezeしたfrozen object○ frozen objectを変更するとランタイムでクラッシュ● 基本的な対応策○ スレッドを跨ぐオブジェクトはImmutableにする○ mutable stateを持つクラスがfreezeされないようにする
native-mt問題● [制約3] iOSメンバーはKotlinの経験がなく、これから学ぶ必要あり○ 注意して開発しよう、は選択できない● 課題を制約に変換して解消○ [制約5] frozenをアーキテクチャ基盤に隠蔽し、ロジックやUIの実装時に意識させない
アーキテクチャの選定● [選択4] MVIベースのアーキテクチャを採用する● ImmutableなIntentをUI側から入力し、ImmutableなUI state / eventを出力してもらう● 各レイヤーで受け渡されるデータがすべてImmutableであれば、基盤側でfreezeしちゃえばnative-mt問題は発生しない
UI層の技術選定● [選択5] UI toolkitには宣言的UIを採用● [制約1] 各OSごとにUIを最適化し、UXを最大化する方針○ OS標準のUI toolkitを選択したい● [選択4] MVIベースのアーキテクチャを採用する○ ImmutableなUI stateが差分ではなく全体として更新通知される
アーキテクチャ概観UIStateHolderUseCaseRepositoryApiClientstate Intentevent
アーキテクチャ概観UIStateHolderUseCaseRepositoryApiClientstate IntenteventIntent → UI state or event
アーキテクチャ概観UIStateHolderUseCaseRepositoryApiClientstate Intentevent[課題5] IntentがAndroidの既存概念とコンフリクトする
アーキテクチャ概観UIStateHolderUseCaseRepositoryApiClientstate Actionevent[選択6] UI層からの入力はActionと命名
自動テスト戦略● [課題2] OSでロジックが異なったことによるインシデント
自動テスト戦略● [課題2] OSでロジックが異なったことによるインシデント● [選択7] 自動結合テスト○ HttpClientにおけるレスポンスのみをmockする■ KMMではKtor clientのMockEngineを活用○ 全Actionに対し、期待となるUI state / eventを検証○ expect / actualを活用し、JVM / iOS両方の環境でのテストをワンソースで実施
テストコードのイメージ@Testfun executeAction_loadInitially_success() = reactorSuspendTest(reactorFactory = ::CouponListReactorFactory,responses = {every { "/v1/coupons/list?limit=10" } returns SUCCESS_RESPONSE_1_3}) { reactor ->reactor.test {reactor.execute(Action.LoadInitially)assertTrue(awaitState() is LoadState.Loading)/* 省略 */}}
CIによるインテグレーション省力化● [課題3] インテグレーションのオーバーヘッド● [選択8] 徹底的なインテグレーションの自動化○ maven repository / XCFrameworkを置く成果物リポジトリ○ KMM側のPRに対応して成果物リポジトリにbranchが生える○ SNAPSHOT運用されるので、そのbranchをrefする○ KMM側のPRが閉じたら成果物リポジトリのbranchも消える○ その他tagを切って過去バージョンをアーカイブしたり、QA期間用のフローも自動化
PoC実装
Cache strategy● [課題6] Cache DBの変更通知をUI stateに反映するフローを自然に表現できないModelUser Action statefun update(prev: Statea: Action): StateServer
Cache strategy● [課題6] Cache DBの変更通知をUI stateに反映するフローを自然に表現できないServer DB?ModelUser Action statefun update(prev: Statea: Action): State
Cache strategy● [課題6] Cache DBの変更通知をUI stateに反映するフローを自然に表現できないServer DBModelUser Action statefun update(prev: Statea: Action): State
Cache strategy● [課題6] Cache DBの変更通知をUI stateに反映するフローを自然に表現できないServer DBModel?statefun update(prev: Statea: Action): StateUser Action
調査● KMM + MVIで調査したところ、Wantedlyさんの記事を発見● https://www.wantedly.com/companies/wantedly/post_articles/300999○ 2020年のKotlin Advent Calendar最終日の記事
ReactorKitから借用する● [選択9] Action -> UI stateの変換を2 stepsに分けるModelUser Action statefun mutate(a: Action): Mutationfun reduce(prev: State,m: Mutation): StateMutationServer DB
結果● スタートポイントの課題は解決○ これまでより品質が担保できる状態● iOSメンバーの感想○ Kotlinの書き方は学ぶ必要があったが、アーキテクチャやiOSだからという理由で困ったことはなかった● 単純にコード量が減ったというだけでなく、書ける人・レビューできる人がこれまでより増えた面も
Why not● 既存のアーキテクチャフレームワークを使わず、自前で基盤を実装● 解くべき課題と制約は事業・チームが異なれば変わる● 何なら時間でも変化する
native-mt問題の完全解決● Kotlin NativeにおけるNew Memory Manager○ インスタンスがスレッドを跨げないという制約がなくなる○ まだexperimentalでnot production ready● これからKMMにtryしたい人は、これがreadyになるのを待ちましょう● 非常に大きな制約が消えるので、必然的にアーキテクチャ設計に影響するでしょう
まとめ● 世のアーキテクチャがシンプル or オーバーエンジニアリングに見えるのは、解くべき課題や制約がそれぞれで異なるから● 課題を解決するアイデアとしてのアーキテクチャの知識は必要● 事業やチームの課題と制約を明確に意識して設計しましょう
アーキテクチャは選定するのではなく設計せよ
Thanks!