Slide 1

Slide 1 text

(Unofficial) Guide to App Architecture Guide Saryong Kang Developer Relations Engineer @ Google 1

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

So.. Is Clean Architecture a design pattern? 7

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

● 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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

Let me prove it. 16

Slide 17

Slide 17 text

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/

Slide 18

Slide 18 text

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/

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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" } } }

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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), "")

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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 }

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

Thanks a lot! 45