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
1.5k
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Mobileアプリのアーキテクチャ設計法
2022/04/21 (木) に開催されたKyash Tech Talk #3の登壇資料です。
Keita Kagurazaka
April 21, 2022
More Decks by Keita Kagurazaka
See All by Keita Kagurazaka
三者三様 宣言的UI
kkagurazaka
0
520
SELECT FOR UPDATEの話
kkagurazaka
0
490
原理から完全理解するDagger Hilt Migration
kkagurazaka
1
2k
今後のJetpackでAndroid開発はこう変わる!
kkagurazaka
16
6.4k
外部SDKのViewにマスク処理をする方法と罠
kkagurazaka
0
1.1k
AWAのフルリニューアルを支えたアーキテクチャ
kkagurazaka
1
970
CQRS Architecture on Android
kkagurazaka
7
3.2k
suspending functionの裏側
kkagurazaka
3
480
coroutinesで非同期ページネーション
kkagurazaka
1
710
Other Decks in Programming
See All in Programming
気づいたらRubyで100作品 ー クリエイティブコーディングが生活の一部になるまで / 100 Ruby Sketches Later: How Creative Coding Became Part of My Life
chobishiba
3
590
Webフレームワークの ベンチマークについて
yusukebe
0
170
jQueryをバージョンアップする前に使いたいjQuery Migrate
matsuo_atsushi
0
560
TSKaigi Night Talks 2026_TypeScriptでサプライチェーンの整合性を型に閉じ込める
geekplus_tech
0
400
TAKTでAI駆動開発の品質を設計する
j5ik2o
7
1.4k
依存関係から依存物へ―Dependencyという言葉の歴史をひも解く
j_lee
0
120
[2026年度第1回ORセミナー] 計画最適化ベンチャーと競技プログラミング人材
terryu16
0
270
ユニットテストの先へ:テスト技法で要求・仕様を整理するJava開発実践 / Beyond_Unit_Testing_Practical_Java_Development_Techniques_for_Organizing_Requirements_and_Specifications
shimashima35
0
410
Lessons from Spec-Driven Development
simas
PRO
0
210
New "Type" system on PicoRuby
pocke
1
980
JJUG CCC 2026 Spring: JSpecify で実現する Kotlin フレンドリーな Java API 設計
ternbusty
1
180
なぜ型を書くのか? TSKaigi2026で改めて考える #tskaigi_smarthr
kajitack
0
100
Featured
See All Featured
Done Done
chrislema
186
16k
Building a A Zero-Code AI SEO Workflow
portentint
PRO
0
600
JAMstack: Web Apps at Ludicrous Speed - All Things Open 2022
reverentgeek
1
480
Statistics for Hackers
jakevdp
799
230k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.5k
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.5k
How to Ace a Technical Interview
jacobian
281
24k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
9
1.4k
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
62
54k
Facilitating Awesome Meetings
lara
57
7k
How to Get Subject Matter Experts Bought In and Actively Contributing to SEO & PR Initiatives.
livdayseo
0
140
From π to Pie charts
rasagy
0
210
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!