Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Mobileアプリのアーキテクチャ設計法
Search
Keita Kagurazaka
April 21, 2022
Programming
2
1.3k
Mobileアプリのアーキテクチャ設計法
2022/04/21 (木) に開催されたKyash Tech Talk #3の登壇資料です。
Keita Kagurazaka
April 21, 2022
Tweet
Share
More Decks by Keita Kagurazaka
See All by Keita Kagurazaka
SELECT FOR UPDATEの話
kkagurazaka
0
330
原理から完全理解するDagger Hilt Migration
kkagurazaka
1
1.8k
今後のJetpackでAndroid開発はこう変わる!
kkagurazaka
16
6k
外部SDKのViewにマスク処理をする方法と罠
kkagurazaka
0
890
AWAのフルリニューアルを支えたアーキテクチャ
kkagurazaka
1
820
CQRS Architecture on Android
kkagurazaka
7
2.9k
suspending functionの裏側
kkagurazaka
3
420
coroutinesで非同期ページネーション
kkagurazaka
1
610
async/awaitで快適非同期ライフ
kkagurazaka
4
1.8k
Other Decks in Programming
See All in Programming
Beyond ORM
77web
11
1.6k
2025.01.17_Sansan × DMM.swift
riofujimon
2
570
Запуск 1С:УХ в крупном энтерпрайзе: мечта и реальность ПМа
lamodatech
0
960
毎日13時間もかかるバッチ処理をたった3日で60%短縮するためにやったこと
sho_ssk_
1
550
どうして手を動かすよりもチーム内のコードレビューを優先するべきなのか
okashoi
3
880
watsonx.ai Dojo #6 継続的なAIアプリ開発と展開
oniak3ibm
PRO
0
170
traP の部内 ISUCON とそれを支えるポータル / PISCON Portal
ikura_hamu
0
190
週次リリースを実現するための グローバルアプリ開発
tera_ny
1
1.2k
いりゃあせ、PHPカンファレンス名古屋2025 / Welcome to PHP Conference Nagoya 2025
ttskch
1
190
PHPとAPI Platformで作る本格的なWeb APIアプリケーション(入門編) / phpcon 2024 Intro to API Platform
ttskch
0
390
20241217 競争力強化とビジネス価値創出への挑戦:モノタロウのシステムモダナイズ、開発組織の進化と今後の展望
monotaro
PRO
0
290
各クラウドサービスにおける.NETの対応と見解
ymd65536
0
250
Featured
See All Featured
The Cult of Friendly URLs
andyhume
78
6.1k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
507
140k
A Modern Web Designer's Workflow
chriscoyier
693
190k
Speed Design
sergeychernyshev
25
740
Designing Dashboards & Data Visualisations in Web Apps
destraynor
230
52k
Practical Orchestrator
shlominoach
186
10k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
3
240
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
251
21k
Documentation Writing (for coders)
carmenintech
67
4.5k
Being A Developer After 40
akosma
89
590k
The World Runs on Bad Software
bkeepers
PRO
66
11k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
44
7k
Transcript
Mobileアプリの アーキテクチャ設計法 〜KMMを例に〜 Keita Kagurazaka
課題と制約
課題と制約 • 現状と理想のギャップが課題 • 選択肢を制限するものが制約 ◦ 技術的なもの ◦ チーム由来のもの ◦
歴史的経緯によるもの • 制約を守りつつ、課題を解決しなければならない • アーキテクチャは課題の解決方法の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が差分ではなく全体として更新通知される
アーキテクチャ概観 UI StateHolder UseCase Repository ApiClient state Intent event
アーキテクチャ概観 UI StateHolder UseCase Repository ApiClient state Intent event Intent
→ UI state or event
アーキテクチャ概観 UI StateHolder UseCase Repository ApiClient state Intent event
アーキテクチャ概観 UI StateHolder UseCase Repository ApiClient state Intent event [課題5]
IntentがAndroidの既 存概念とコンフリクトする
アーキテクチャ概観 UI StateHolder UseCase Repository ApiClient state Action event [選択6]
UI層からの入力は Actionと命名
自動テスト戦略 • [課題2] OSでロジックが異なったことによるインシデント
自動テスト戦略 • [課題2] OSでロジックが異なったことによるインシデント • [選択7] 自動結合テスト ◦ HttpClientにおけるレスポンスのみをmockする ▪
KMMではKtor clientのMockEngineを活用 ◦ 全Actionに対し、期待となるUI state / eventを検証 ◦ expect / actualを活用し、JVM / iOS両方の環境でのテストをワン ソースで実施
テストコードのイメージ @Test fun 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) /* 省略 */ } }
テストコードのイメージ @Test fun 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) /* 省略 */ } }
テストコードのイメージ @Test fun 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) /* 省略 */ } }
テストコードのイメージ @Test fun 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に反映するフローを自然に表現 できない Model User
Action state fun update( prev: State a: Action ): State Server
Cache strategy • [課題6] Cache DBの変更通知をUI stateに反映するフローを自然に表現 できない Server DB
? Model User Action state fun update( prev: State a: Action ): State
Cache strategy • [課題6] Cache DBの変更通知をUI stateに反映するフローを自然に表現 できない Server DB
Model User Action state fun update( prev: State a: Action ): State
Cache strategy • [課題6] Cache DBの変更通知をUI stateに反映するフローを自然に表現 できない Server DB
Model ? state fun update( prev: State a: Action ): State User Action
調査 • KMM + MVIで調査したところ、Wantedlyさんの記事を発見 • https://www.wantedly.com/companies/wantedly/post_articles/300999 ◦ 2020年のKotlin Advent
Calendar最終日の記事
ReactorKitから借用する • [選択9] Action -> UI stateの変換を2 stepsに分ける Model User
Action state fun mutate( a: Action ): Mutation fun reduce( prev: State, m: Mutation ): State Mutation Server DB
結果 • スタートポイントの課題は解決 ◦ これまでより品質が担保できる状態 • iOSメンバーの感想 ◦ Kotlinの書き方は学ぶ必要があったが、アーキテクチャやiOSだから という理由で困ったことはなかった
• 単純にコード量が減ったというだけでなく、書ける人・レビューできる人がこ れまでより増えた面も
Why not • 既存のアーキテクチャフレームワークを使わず、自前で基盤を実装 • 解くべき課題と制約は事業・チームが異なれば変わる • 何なら時間でも変化する
native-mt問題の完全解決 • Kotlin NativeにおけるNew Memory Manager ◦ インスタンスがスレッドを跨げないという制約がなくなる ◦ まだexperimentalでnot
production ready • これからKMMにtryしたい人は、これがreadyになるのを待ちましょう • 非常に大きな制約が消えるので、必然的にアーキテクチャ設計に影響する でしょう
まとめ • 世のアーキテクチャがシンプル or オーバーエンジニアリングに見えるの は、解くべき課題や制約がそれぞれで異なるから • 課題を解決するアイデアとしてのアーキテクチャの知識は必要 • 事業やチームの課題と制約を明確に意識して設計しましょう
アーキテクチャは 選定するのではなく 設計せよ
Thanks!