Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[JA] (Unofficial) Guide to App Architecture Guide (Vol 1) - DroidKaigi 2022

[JA] (Unofficial) Guide to App Architecture Guide (Vol 1) - DroidKaigi 2022

Sa-ryong Kang

October 05, 2022
Tweet

More Decks by Sa-ryong Kang

Other Decks in Technology

Transcript

  1. (Unofficial) Guide to
    App Architecture Guide
    カン・サリョン (Saryong Kang, justfaceit_kr@)
    Developer Relations Engineer @ Google
    1
    English Version:
    https://speakerdeck.com/saryong/en-unofficial-g
    uide-to-app-architecture-guide-droidkaigi-2022

    View full-size slide

  2. 2
    ● Google
    ● Android Policy / Compatibility Program
    Software Engineer
    Jing Ji
    Saryong Kang
    Developer Relations Engineer
    Andoird Engineering所属
    Device毎の機能の一貫性が主な仕事 (特にbg task)

    View full-size slide

  3. 免責事項
    ● この講義の内容はあくまで個人の意見です。
    ● Googleの公式見解とは異なる可能性があります。
    3

    View full-size slide

  4. Agenda
    App Architecutre Guideが話さない事
    ドメイン層
    データ層
    UI層
    01
    02
    03
    04
    4

    View full-size slide

  5. Agenda
    App Architecutre Guideが話さない事
    ドメイン層
    データ層
    UI層
    01
    02
    03
    04
    5

    View full-size slide

  6. Clean Architecture
    ● モバイルアプリ設計にクリーンアーキテクチャを適用するための昔からの
    議論
    ○ SOLID原則
    ○ レイヤ別のコンポーネント
    ● 例: 2015年のブログ記事
    6

    View full-size slide

  7. 7
    Source: https://qiita.com/koutalou/items/07a4f9cf51a2d13e4cdc
    注意: Googleの公式な意見と関係ありません

    View full-size slide

  8. So..
    Is Clean Architecture
    a design pattern?
    8

    View full-size slide

  9. Clean Architecture is more like
    a way of thinking
    than
    a design / architectural pattern.
    9

    View full-size slide

  10. Clean Architecture is not a pattern
    ● クリーンアーキテクチャから得られる重要なインサイトは、
    レイヤ毎に分けられた構造がモバイルアプリ設計にも役に立つ事
    ● ただ、
    ○ レイヤの具体的なコンポーネントは定義されていない
    ○ 前のページの図はその模範事例の一つにすぎない
    10

    View full-size slide

  11. 11
    Source (left): https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
    Source (right): https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

    View full-size slide

  12. Yes, Clean Architecture affected The Guide a lot
    ● Layered architecture: UI層 - Domain層 - Data層
    ● SOLID 原則, 特に単一責務の原則(Single Responsibility Principle)
    ● だが、クリーンアーキテクチャの実現が目標ではない
    12

    View full-size slide

  13. なぜこういう概念はカバーしていない?
    ● MVI (Flux, Redux), MVP, …
    ○ Because they are not important? No
    ● 全てのパターン・アーキテクチャーを網羅するのが目的ではない
    ○ 一般的なbest practice + 推奨事項
    13

    View full-size slide

  14. Agenda
    App Architecutre Guideが話さない事
    ドメイン層
    データ層
    UI層
    01
    02
    03
    04
    14

    View full-size slide

  15. 何故ドメイン層が重要なのか
    ● UI層とデータ層の間の仲介がない時に生じる設計上の問題を解決できる
    ○ ドメイン層を実装しない場合でも、考察が必要
    ● UI層に集中しすぎると、
    ○ 画面中心思考 → 画面の区別を超える対応が難しくなる
    ○ Agile逆効果 → 画面の要素がそのままbacklog/featureになり、
    classになってしまう
    ● データ層に集中しすぎると、
    ○ データスキーマがドメイン/UI層まで影響 → 典型的な設計ミス
    15

    View full-size slide

  16. Challenge: as an Android Engineer,
    we may not have enough
    design experiences.
    16

    View full-size slide

  17. Let me prove it.
    17

    View full-size slide

  18. Coffee Maker Mk IV Case Study
    ● 一連のコーヒーメーカー (Mark Vもいつか発売さ
    れるはず)
    ● 保温プレートはポットを長時間暖める
    ● 手順
    a. コーヒー粉をフィルターに入れ、バスケットを
    閉じる
    b. 水を水タンクに入れ、「抽出」ボタン押下
    c. 加熱された水が蒸気化
    d. 蒸気口が開けられ、コーヒー粉に噴射される
    e. 抽出されたコーヒーはフィルタを通過しポット
    に入れる
    18
    Source: https://cleancoders.com/episode/clean-code-episode-15
    Image source: https://www.cafeappliances.com/

    View full-size slide

  19. Coffee Maker Mk IV Case Study
    ● 加熱器 (boiler; can be turned on / off)
    ● 保温プレート (Warmer plate; on / off)
    ● 保温プレートセンサー
    (状態: warmerEmpty, potEmpty,
    potNotEmpty.
    ● 加熱器センサー
    (状態: boilerEmpty, boilerNotEmpty)
    ● 抽出ボタン + ランプ
    ● 蒸気口
    19
    Source: https://cleancoders.com/episode/clean-code-episode-15
    Image source: https://www.cafeappliances.com/

    View full-size slide

  20. Looks nice?
    20
    Source: https://cleancoders.com/episode/clean-code-episode-15

    View full-size slide

  21. Think again: is that a screaming architecture?
    21
    ● 手順
    a. コーヒー粉をフィルターに入れ、バスケットを閉じる
    b. 水を水タンクに入れ、「抽出」ボタン押下
    c. 加熱された水が蒸気化
    d. 蒸気口が開けられ、コーヒー粉に噴射される
    e. 抽出されたコーヒーはフィルタを通過しポットに入れる

    View full-size slide

  22. Design example: Coffee Maker Mark IV
    22
    Source: https://cleancoders.com/episode/clean-code-episode-15

    View full-size slide

  23. Design example: Coffee Maker Mark IV
    23
    Source: https://cleancoders.com/episode/clean-code-episode-15

    View full-size slide

  24. Insight
    ● データー及びエンティティ中心の思考ではいい設計が出来ない
    ● 逆に、核心行為とactorを抽象化する形がもっと重要
    ● 上記の思想を仲介者の設計に適用すると、色んな形で設計ができる
    ○ 1. DDDに近い設計
    ○ 1-1. Use Caseを利用
    ○ 2. non-domain mediator layer
    例: Gateway, Mapper, Data Controller, Translator, …
    ○ 3. データ層で吸収: Repository
    24

    View full-size slide

  25. Agenda
    App Architecutre Guideが話さない事
    ドメイン層
    データ層
    UI層
    01
    02
    03
    04
    25

    View full-size slide

  26. Repository pattern
    ● よくある誤解: Repository in Android is anti-pattern!?
    ○ It violates Single Responsibility Principle?!
    ● Repositoryは、ドメイン層(又はUI層)から呼び出されるための抽象を提供
    ○ 他のレイヤに見せるためのinterface提供
    ○ データ格納に関する具体的な事項を隠してくれる
    26

    View full-size slide

  27. Data Source
    ● データIOの実装を隠してくれる
    ● Liskov置換原則 (LSP)
    ○ 継承側で実装を切り替えても動作は変わらない
    ■ REST ↔ gRPC
    ■ Room ↔ other ORM
    ■ Real 実装 ↔ Fake
    27

    View full-size slide

  28. データ層の考慮事項
    ● CRUD類の簡単な操作だけなら、
    Repository / Data Source 両方が必要ないかも
    ● Repositoryのライフサイクル
    ● Who should be the Single Source of Truth in your app?
    28

    View full-size slide

  29. Agenda
    App Architecutre Guideが話さない事
    ドメイン層
    データ層
    UI層
    01
    02
    03
    04
    29

    View full-size slide

  30. What AAC ViewModel does for you
    ● View Model in Ancdroid Architecutre Components:
    一般的な実装に役に立つ最小限の機能を提供
    ○ 安全な状態格納のための仕組み
    ■ Safe from configuration change (by default)
    ■ Safe from process kill (thru SavedStateHandler)
    ○ Coroutine Scope (with some caveats)
    ○ 依存性注入 via Dagger Hilt
    ○ Helper for Jetpack Navigation
    30
    注意: Googleの公式な意見と関係ありません

    View full-size slide

  31. What AAC ViewModel doesn’t do for you
    ● However,
    ○ it’s completely your job to make VM VM-like
    ⇒ Adopting AAC VM doesn’t automatically mean you built a
    good MVVM architecture.
    ○ it may not appropriate for some use cases
    ○ sometimes implementation of VM seems too verbose
    31

    View full-size slide

  32. If you don’t like it,
    ● You can build your own!
    ○ And AAC ViewModel source code will inspire you about how
    to implement Saved State Handler, its own Life Cycle,
    Coroutines Scope, Navigation, etc.
    32

    View full-size slide

  33. Another Downside of ViewModel: Verbosity
    ● Circular event flow
    ○ (1) Viewからイベント発生
    ○ (2) ViewModelが処理し、
    Stateを変更
    ○ (3) View側で受け取り、
    画面に描画
    33
    override fun onViewCreated(...) {
    // (1)
    binding.plusButton.setOnClickListener { _ ->
    viewModel.incrementCounter()
    }
    // (3)
    viewModel.counter.observe(viewLifeCycleOwner) {
    binding.plusButton.text = "Count: $it"
    }
    }
    class CounterViewModel {
    private val _counter = MutableStateFlow(0)
    val counter: StateFlow
    get() = _counter.asStateFlow()
    fun incrementCounter() {
    // (2)
    _counter.value += 1
    // something aync...
    }
    }

    View full-size slide

  34. Solution
    ● boiler plateを許容
    ● Data Binding! - can solve this in elegant way
    34

    View full-size slide

  35. Consideration on Jetpack Compose
    ● MVP doesn't make sense in many cases
    ○ View ↔ Presenterの相互作用がメソッド呼び出しで行われる
    ○ 宣言型(declarative) Viewの場合は微妙
    ● ViewModel seems to make more sense, but.. does it?
    35

    View full-size slide

  36. Consideration on Jetpack Compose
    ● Is ViewModel really necessary to me?
    ○ rememberSavable: AAC ViewModelが提供していたstateをView
    側で定義可能
    ○ データレイヤのrepositoryが別途のlife cycleを持っている場合、
    ■ stateの格納をViewModelに委任する必要があるの?
    ■ VMがなくてもよく動作する場面が多い
    (if ドメイン or データレイヤが十分なビジネスルールを実装してい
    る場合 + AAC VMの便利機能が要らない場合)
    36

    View full-size slide

  37. Consideration on Jetpack Compose
    ● Be cautious about construction
    ○ What is wrong with the code in the next slide?
    37

    View full-size slide

  38. 38
    @Composable
    fun MyComposable(
    viewModel: MyViewModel = hiltViewModel()
    ) {
    Text(text = viewModel.myState)
    }
    class MyViewModel : ViewModel() {
    private val _myState = mutableStateOf("A")
    val myState: State = _myState
    init {
    viewModelScope.launch(Dispatchers.IO) {
    myState = "B"
    }
    }
    }

    View full-size slide

  39. Consideration on Jetpack Compose
    ● Be cautious about construction
    ○ 1. バックグランドスケジューラで Composableのsnapshot stateを変更
    → Crash!
    ○ 2. 非同期処理 in constructor:
    coroutineScope.launch 中の非同期処理が終わる前にcontructorが
    終了される可能性が高い
    → エラー時のデバッグが非常に難しい、テストコードが書きにくい
    ○ 3. 単一責務の原則(SRP)違反:
    そもそもconstructorで簡単なinstantiation以外の処理をするのは望ま
    しくない
    39

    View full-size slide

  40. Consideration on Jetpack Compose
    ● Be cautious about construction
    ○ 提案
    ■ Implement separated init() method
    ■ Or, run initial job when view starts collection /
    subscription
    40

    View full-size slide

  41. 41
    private val _myState = MutableStateFlow("A")
    val myState: StateFlow = _myState.asStateFlow()
    .onSubscription {
    // initial loading from local db...
    }
    .map { state ->
    // when _myState updated..
    }
    .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), "")

    View full-size slide

  42. Consideration on Jetpack Compose
    ● Be cautious about the scope
    ○ Don’t add anything that may affect the snapshot
    ○ Don’t store UI state (eg. animation) to VM
    42

    View full-size slide

  43. Consideration on Jetpack Compose
    ● Be cautious about the scope
    ○ ViewModelScope uses Dispatchers.Main.immediate
    ■ instead, you can now use custom CoroutineScope
    https://developer.android.com/jetpack/androidx/release
    s/lifecycle#2.5.0 (addClosable(), 新しいconstructor of
    VM)
    43

    View full-size slide

  44. 44
    class CloseableCoroutineScope(
    context: CoroutineContext = SupervisorJob() + Dispatchers.Main
    ) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context
    override fun close() {
    coroutineContext.cancel()
    }
    }
    class MyViewModel(
    val customScope: CloseableCoroutineScope = CloseableCoroutineScope()
    ) : ViewModel(customScope) {
    // You can now use customScope in the same way as viewModelScope
    }

    View full-size slide

  45. Guide to App Architecture Guide Vol. 2
    ● 依存性注入
    ○ Dagger Hiltの長所、短所
    ○ 他の選択肢はないか
    ● Multimodule
    ○ Large scale modular architecture
    ○ マルチモジュール化はいつした方がいい?
    ○ 何故私のマルチモジュールはビルドが遅い?
    45

    View full-size slide

  46. ご清聴ありがとうございました!
    46

    View full-size slide