$30 off During Our Annual Pro Sale. View Details »

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

[EN] (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
    Developer Relations Engineer @ Google
    1

    View Slide

  2. Disclaimer
    ● The opinions stated here are my own, not necessarily those of Google.
    2

    View Slide

  3. Agenda
    What App Architecture Guide doesn’t tell you
    Domain Layer
    Data Layer
    UI Layer
    01
    02
    03
    04
    3

    View Slide

  4. Agenda
    What App Architecture Guide doesn’t tell you
    Domain Layer
    Data Layer
    UI Layer
    01
    02
    03
    04
    4

    View Slide

  5. Clean Architecture
    ● There have been long discussion on how to adopt Clean
    Architecture into mobile app design
    ○ SOLID principle
    ○ Layered component architecture
    ● E.g. Blog post in 2015
    5

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  9. ● The important implication from Clean Architecture is that the
    layered structure helps design of mobile application
    ● However,
    ○ there’s no specific definition of components in each layer
    ○ the diagram in previous page just shows one example of best
    practices
    9
    Clean Architecture is not a pattern

    View Slide

  10. 10
    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 Slide

  11. Yes, Clean Architecture affected Arch. Guide a lot
    ● Layered architecture: UI - Domain - Data Layer
    ● SOLID principle, especially Single Responsibility Principle
    ● However, it's not an implementation of Clean Architecture
    11

    View Slide

  12. Why doesn’t the Guide cover these concepts?
    ● MVI (Flux, Redux), MVP, …
    ○ Not because they are not important
    ● The guide doesn’t intend to cover every patterns and architecture
    that make sense in mobile design
    ○ It’s curated collection of best practices and recommendations
    for most common use cases
    12

    View Slide

  13. Agenda
    What App Architecture Guide doesn’t tell you
    Domain Layer
    Data Layer
    UI Layer
    01
    02
    03
    04
    13

    View Slide

  14. Why is “Domain” so important?
    ● It may solve design problems caused differences between UI and Data
    layer.
    ○ Even when you don’t need to implement domain “layer”, you need to
    consider about the mediator
    ● When you focus UI layer too much,
    ○ Screen-centric design: it makes difficult deal with logics beyond
    screen
    ○ Agile side effect: elements of screen became backlog / features;
    then class
    ● When you focus data layer too much,
    ○ Data schema affects domain / UI layer directly → typical
    antipattern
    14

    View Slide

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

    View Slide

  16. Let me prove it.
    16

    View Slide

  17. Coffee Maker Case Study
    ● A series of coffee makers (Mark V will come
    out eventually)
    ● Warmer plate keeps the pot warm for
    extended time
    a. Put coffee grounds into the filter and
    slide the filter in
    b. Pour water in the water strainer and
    press Brew button
    c. Water is heated until boiling
    d. The pressure of the steam forces the
    water to be sprayed over the coffee
    grounds
    e. Coffee drips through the filter into the
    pot
    17
    Source: https://cleancoders.com/episode/clean-code-episode-15
    Image source: https://www.cafeappliances.com/

    View Slide

  18. Coffee Maker Case Study
    ● Boiler (can be turned on / off)
    ● Warmer plate (can be turned on / off)
    ● Sensor for the warmer plate
    (3 states: warmerEmpty, potEmpty,
    potNotEmpty.
    ● Sensor for the boiler
    (2 states: boilerEmpty, boilerNotEmpty)
    ● Brew button + Light indicator
    ● A pressure-relief valve
    18
    Source: https://cleancoders.com/episode/clean-code-episode-15
    Image source: https://www.cafeappliances.com/

    View Slide

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

    View Slide

  20. Think again: is that a screaming architecture?
    20
    ● A series of coffee makers (Mark V will come out
    eventually)
    ● Warmer plate keeps the pot warm for extended time
    a. Put coffee grounds into the filter and slide the filter in
    b. Pour water in the water strainer and press Brew button
    c. Water is heated until boiling
    d. The pressure of the steam forces the water to be
    sprayed over the coffee grounds
    e. Coffee drips through the filter into the pot

    View Slide

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

    View Slide

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

    View Slide

  23. Insight
    ● Data- or entity-centric thinking can give you good design
    ● Instead, abstracting key actions and actors is more important
    ● You can design mediator like the following:
    ○ 1. DDD-ish design
    ○ 1-1. Use Case (Transaction Script, which is anti-pattern in DDD)
    ○ 2. non-domain mediator layer
    eg. Gateway, Mapper, Data Controller, Translator, …
    ○ 3. put them into Data Layer: Repository
    23

    View Slide

  24. Agenda
    What App Architecture Guide doesn’t tell you
    Domain Layer
    Data Layer
    UI Layer
    01
    02
    03
    04
    24

    View Slide

  25. Repository pattern
    ● Common misconception: Repository in Android is anti-pattern!?
    ○ It violates Single Responsibility Principle?!
    ● Repository provides abstraction to be called from domain (or UI)
    layer
    ○ It hides details of data saving / loading
    25

    View Slide

  26. Data Source
    ● Data Source hides implementation of data I/O
    ● Liskov Substitution Principle (LSP)
    ○ When replacing a subclass with another, its behavior
    shouldn’t be changed
    ■ REST ↔ gRPC
    ■ Room ↔ other ORM
    ■ Real impl. ↔ Fake
    26

    View Slide

  27. Considerations on Data Layer
    ● If you only have simple operations like CRUD,
    you may not need both of Repository and Data Source
    ● Consider a separated life cycle in Repository
    ● Who should be the Single Source of Truth in your app?
    27

    View Slide

  28. Agenda
    What App Architecture Guide doesn’t tell you
    Domain Layer
    Data Layer
    UI Layer
    01
    02
    03
    04
    28

    View Slide

  29. What AAC ViewModel does for you
    ● ACC VM provides minimal features to help developers implement
    general VM
    ○ Mechanism to store state safely
    ■ Safe from configuration change (by default)
    ■ Safe from process kill (thru SavedStateHandler)
    ○ Coroutine Scope
    ○ Dependency Injection via Dagger Hilt
    ○ Helper for Jetpack Navigation
    29

    View Slide

  30. What AAC ViewModel does 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
    30

    View Slide

  31. 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.
    31

    View Slide

  32. Another Downside of ViewModel: Verbosity
    ● Circular event flow
    ○ (1) View event occurred
    ○ (2) ViewModel deal with it
    and then modify the state
    ○ (3) View observes
    changed state, then show
    it to the screen
    32
    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 Slide

  33. Solution
    ● Allow boiler plate
    ● Data Binding! - can solve this in elegant way
    33

    View Slide

  34. Consideration on Jetpack Compose
    ● MVP doesn't make sense in many cases
    ○ Interaction between View and Presenter is done by method
    call
    ○ Declarative View can’t change the screen by one method call
    (without help from additional mediator)
    ● ViewModel seems to make more sense, but.. does it?
    34

    View Slide

  35. Consideration on Jetpack Compose
    ● Is ViewModel really necessary to me?
    ○ rememberSavable: the safe state storage provided by AAC
    ViewModel is now possible in Composable function
    ○ If repository in Data layer has independent life cycle from
    View,
    ■ Is it necessary to yield state store to ViewModel?
    ■ Some cases it works well without VM
    (if domain or data layer containes sufficient business
    logic)
    35

    View Slide

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

    View Slide

  37. 37
    @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 Slide

  38. Consideration on Jetpack Compose
    ● Be cautious about construction
    ○ 1. It changes snapshot state in background scheduler
    → Crash!
    ○ 2. Async task in constructor:
    The async block in coroutineScope.launch could be
    finished after the construction was completed
    → hard to debug when error occurred
    → make it hard to write test code
    ○ 3. Violating Single Responsibility Principle:
    The main responsibility of constructor is a short and
    simple instantiation of fields.
    38

    View Slide

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

    View Slide

  40. 40
    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 Slide

  41. 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
    41

    View Slide

  42. 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(), new constructor of VM)
    42

    View Slide

  43. 43
    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 Slide

  44. Guide to App Architecture Guide Vol. 2
    ● Dependency Injection
    ○ Upside and downside of Dagger Hilt
    ○ Alternatives
    ● Multimodule
    ○ Large scale modular architecture
    ○ When is the best timing to separate modules?
    ○ Why does my multi module project build so slowly?
    44

    View Slide

  45. Thanks a lot!
    45

    View Slide