Slide 1

Slide 1 text

November 25, 2019 Android at Scale @Square Ralf Wondratschek @vRallev

Slide 2

Slide 2 text

:app

Slide 3

Slide 3 text

:app

Slide 4

Slide 4 text

:app1 :app2

Slide 5

Slide 5 text

:lib1 :app2 :app1

Slide 6

Slide 6 text

:app1 :app2

Slide 7

Slide 7 text

:lib1 :app2 :app1

Slide 8

Slide 8 text

:hairball :app2 :app1 :lib1 :lib2

Slide 9

Slide 9 text

Dependency hierarchy :apps :feature :common :api

Slide 10

Slide 10 text

● Login? ● Checkout? ● Settings? ● Hairball? Dependency hierarchy

Slide 11

Slide 11 text

Dependency hierarchy :apps :hairball-feature :hairball :feature :common :api :delegates

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

:hairball :app2 :app1 :lib1 :lib2 Number of modules and lines of code grow

Slide 14

Slide 14 text

:hairball :app2 :app1 :lib1 :lib2 :lib3 :lib4 :lib5 :lib6 Number of modules and lines of code grow

Slide 15

Slide 15 text

● We have a mono repository Mono or multi repository?

Slide 16

Slide 16 text

Number of modules and lines of code grow

Slide 17

Slide 17 text

Number of modules and lines of code grow

Slide 18

Slide 18 text

Number of modules and lines of code grow

Slide 19

Slide 19 text

:hairball :app2 :app1 :lib1 :lib2 :lib3 :lib4 :lib5 :lib6 Weight matters

Slide 20

Slide 20 text

:hairball :app2 :app1 :lib1 :lib2 :lib3 :lib4 :lib5 :lib6 Weight matters

Slide 21

Slide 21 text

● Logical unit to perform work ● Self-contained piece of the whole system ● Loose coupling ● High cohesion What is a module in modular design?

Slide 22

Slide 22 text

● Code is easier portable ● Code is more reliable ● Code is easier to test ● Code is easier to maintain ● ... Advantages of modular design?

Slide 23

Slide 23 text

class Feature(b: LoginStrategy) class PosLoginStrategy() Dependency inversion

Slide 24

Slide 24 text

class Feature(b: LoginStrategy) interface LoginStrategy class PosLoginStrategy() : LoginStrategy Dependency inversion

Slide 25

Slide 25 text

● With multiple products? ○ Yes!!! ● With multiple modules? ○ Yes!! ● With a single module? ○ Yes! ● Without tests? ○ ¯\_(ツ)_/¯ Do we need dependency inversion?

Slide 26

Slide 26 text

interface LoginStrategy { fun login() } class PosLoginStrategy @Inject constructor( private val helper: Helper ) : LoginStrategy { override fun login() { } @dagger.Module abstract class Module { @Binds abstract fun bindLoginStrategy(strategy: PosLoginStrategy): LoginStrategy } } Dependency inversion

Slide 27

Slide 27 text

class PosLoginStrategy @Inject constructor( private val helper: Helper, private val helper: Helper1, private val helper: Helper2, private val helper: Helper3, private val helper: Helper4, private val helper: Helper5, private val helper: Helper6, private val helper: Helper7, private val helper: Helper8 ) : LoginStrategy Dependencies

Slide 28

Slide 28 text

dependencies { api project(':helper1') api project(':helper2') api project(':helper3') api project(':helper4') api project(':helper5') api project(':helper6') api project(':helper7') api project(':helper8') } Dependencies

Slide 29

Slide 29 text

Dependencies class Feature(b: LoginStrategy) interface LoginStrategy class PosLoginStrategy() : LoginStrategy

Slide 30

Slide 30 text

Dependencies class Feature(b: LoginStrategy) interface LoginStrategy

Slide 31

Slide 31 text

dependencies { api project(':login-strategy') } Dependencies for Feature

Slide 32

Slide 32 text

dependencies { api project(':login-strategy') api project(':helper1') api project(':helper2') api project(':helper3') api project(':helper4') api project(':helper5') api project(':helper6') api project(':helper7') api project(':helper8') } Dependencies for Feature

Slide 33

Slide 33 text

Module Structure :public

Slide 34

Slide 34 text

Module Structure :public :impl

Slide 35

Slide 35 text

Module Structure :public :impl :impl-wiring

Slide 36

Slide 36 text

:login-strategy Module Structure :public :impl :impl-wiring

Slide 37

Slide 37 text

Module Structure :login-strategy:public :login-strategy:impl :login-strategy:impl-wiring

Slide 38

Slide 38 text

Module Structure

Slide 39

Slide 39 text

:login-strategy Module Structure :public :impl :impl-wiring :feature :public :impl :impl-wiring X

Slide 40

Slide 40 text

Legacy modules ● All the rules of the new module structure don’t apply ○ (They’re all the module types in one) ● This doesn’t mean you shouldn’t care about dependencies and their size!

Slide 41

Slide 41 text

Module Structure API Implementation Wiring

Slide 42

Slide 42 text

// :public interface LoginStrategy { fun login() } // :impl class PosLoginStrategy @Inject constructor( private val helper: Helper ) : LoginStrategy { override fun login() { } } // :impl-wiring @dagger.Module abstract class LoginStrategyModule { @Binds abstract fun bindLoginStrategy(strategy: PosLoginStrategy): LoginStrategy } Module Structure

Slide 43

Slide 43 text

dependencies { api project(':login-strategy') api project(':helper1') api project(':helper2') api project(':helper3') api project(':helper4') api project(':helper5') api project(':helper6') api project(':helper7') api project(':helper8') } Module Structure

Slide 44

Slide 44 text

dependencies { api project(':login-strategy:public') } Module Structure

Slide 45

Slide 45 text

Dependency hierarchy :apps :feature :common :api

Slide 46

Slide 46 text

Dependency hierarchy :apps :shared

Slide 47

Slide 47 text

Module Structure

Slide 48

Slide 48 text

Development Apps :public :impl :impl-wiring

Slide 49

Slide 49 text

Development Apps :public :impl :impl-wiring :demo

Slide 50

Slide 50 text

Development Apps

Slide 51

Slide 51 text

Development Apps ● Install a feature with an isolated application on the device ○ Launch the feature directly ○ Shorter build times ● Replace dependencies by fakes ● Isolated UI tests

Slide 52

Slide 52 text

Workflows https://square.github.io/workflow/

Slide 53

Slide 53 text

:account-screen:public interface AccountScreenWorkflow : Workflow>

Slide 54

Slide 54 text

:login-screen:impl class SampleLoginScreenWorkflow @Inject constructor( private val accountScreenWorkflow: Provider ) : LoginScreenWorkflow, StatefulWorkflow>() { override fun render( props: Unit, state: LoginScreenState, context: RenderContext ): LayeredScreen { return when (state) { is RenderAccountScreen -> { context.renderChild(accountScreenWorkflow.get(), Unit) { leaveAccountScreen } } } } }

Slide 55

Slide 55 text

:account-screen:fake class FakeAccountScreenWorkflow @Inject constructor() : AccountScreenWorkflow, StatelessWorkflow>() { // ... }

Slide 56

Slide 56 text

:login-screen:demo android { defaultConfig { applicationId "com.squareup.sample.login.screen.demo" } } dependencies { // … implementation project(':samples:account-screen-sample:account-screen:fake-wiring') }

Slide 57

Slide 57 text

Dependency Graph :camera-screen :profile-photo-screen :edit-account-screen :account-screen:impl :account-screen:public

Slide 58

Slide 58 text

Dependency Graph :account-screen:public :account-screen:fake

Slide 59

Slide 59 text

Fakes ● Reduce size of the dependency graph ● Reduce the number of lines of code that need to be compiled ● Allow changing behavior as you wish

Slide 60

Slide 60 text

Build times (in seconds)

Slide 61

Slide 61 text

Benchmark build times ● https://github.com/gradle/gradle-profiler

Slide 62

Slide 62 text

Benchmark build times

Slide 63

Slide 63 text

Summary ● Square isn’t different, has its own set of unique problems ● Modularization is an ongoing process, celebrate as you make progress ● Start thinking about dependencies and build time early in the process ● Make use of design patterns like dependency inversion

Slide 64

Slide 64 text

Q&A

Slide 65

Slide 65 text

Ralf Wondratschek @vRallev