Slide 1

Slide 1 text

Modularizing Legacy Apps A PRAGMATIC APPROACH Marcello Galhardo Slides at https://tinyurl.com/y9zcf4u9

Slide 2

Slide 2 text

Marcello Galhardo Android Dev @ Quandoo Moderator @ Android Dev BR

Slide 3

Slide 3 text

Why? • Build takes too long • Code ownership • Separation of concerns • Dynamic Features

Slide 4

Slide 4 text

Strategy #1 Quality Directives

Slide 5

Slide 5 text

• Code must be under continuous integration • Automate the build • Test coverage measure the code quality • Small Feature Branches and Pull Requests

Slide 6

Slide 6 text

• Code must be under continuous integration • Automate the build • Test coverage measure the code quality • Small Feature Branches and Pull Requests

Slide 7

Slide 7 text

Write code Push code to Git Run unit and ui tests Build generation Deploy on Store Distribute to QAs

Slide 8

Slide 8 text

• Code must be under continuous integration • Automate the build • Test coverage measure the code quality • Small Feature Branches and Pull Requests

Slide 9

Slide 9 text

[#ticket] New search page in separated module ? authored 2 weeks ago Showing 272 changed files with 4582 additions and 365 deletions

Slide 10

Slide 10 text

New Branch Your Branch Cherry-pick Cherry-pick PR 1 PR 2 Develop Branch

Slide 11

Slide 11 text

Strategy #2 Scout the Codebase

Slide 12

Slide 12 text

• Keep things you need • Donate things someone else might need • Discard or recycle the rest

Slide 13

Slide 13 text

fun View.isVisible() = this.visibility == View.VISIBLE @Deprecated( message = "Replaced by ViewKt.", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith( expression = "ViewKt" ) ) object ViewUtils {…}

Slide 14

Slide 14 text

Strategy #3 Keep It Simple, Stupid

Slide 15

Slide 15 text

• Don’t create modules that you don’t need • Don’t plan too much ahead: Emergent Design • There are no final decisions • "Branch by Abstraction” when extracting modules

Slide 16

Slide 16 text

Strategy #4 Don’t Talk to Strangers

Slide 17

Slide 17 text

• Modules shouldn't reveal anything unnecessary to other modules • Modules don’t rely on other modules' implementations • Explicitly pass required data into your modules • Avoid similar modules: duplication is a symptom of structural problems

Slide 18

Slide 18 text

Strategy #5 Don’t Repeat Yourself

Slide 19

Slide 19 text

• Make it easier to share code • Centralise your resources • Share versions of dependencies using variables

Slide 20

Slide 20 text

• Make it easier to share code • Centralise your resources • Share versions of dependencies using variables

Slide 21

Slide 21 text

App

Slide 22

Slide 22 text

Feature 2 Feature 1

Slide 23

Slide 23 text

Feature 2 Feature 1 Common

Slide 24

Slide 24 text

Feature 2 Feature 1 Common

Slide 25

Slide 25 text

Build Output :feature2 :feature1 :base Use Use

Slide 26

Slide 26 text

• Make it easier to share code • Centralise your resources • Share versions of dependencies using variables

Slide 27

Slide 27 text

// ../scripts/gradle/base-build.gradle apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: ‘kotlin-kapt' android { compileSdkVersion compile_sdk_version buildToolsVersion build_tools_version defaultConfig { minSdkVersion min_sdk_version targetSdkVersion target_sdk_version versionCode version_code versionName version_name } buildTypes {…} } dependencies { implementation “org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" }

Slide 28

Slide 28 text

// Feature module apply plugin: 'com.android.feature' apply from: "../scripts/gradle/base-build.gradle" dependencies {…} // Library module apply plugin: 'com.android.library' apply from: “../scripts/gradle/base-build.gradle" dependencies {…}

Slide 29

Slide 29 text

Strategy #6 Small Interactions

Slide 30

Slide 30 text

:legacy :feature :facade Invokes new feature Access legacy functionalities by well-defined interface and dependency injection Implements interfaces on legacy code

Slide 31

Slide 31 text

:legacy :feature2 :facade :feature1 :feature3 Extract features from :legacy

Slide 32

Slide 32 text

:app :feature2 :base :feature1 :feature3 Move interfaces from facade and provide new Implementation to :base

Slide 33

Slide 33 text

IntelliJ IDEA rocks. Luckily, Android Studio is powered by IntelliJ IDEA. https://www.jetbrains.com/help/idea/refactoring- source-code.html

Slide 34

Slide 34 text

Strategy #7 Vertical Slice Architecture

Slide 35

Slide 35 text

• Avoid “N-Tier Architectures” • Architectural boundaries don’t run between modules but rather through them

Slide 36

Slide 36 text

• Avoid “N-Tier Architectures” • Architectural boundaries don’t run between modules but rather through them

Slide 37

Slide 37 text

:app :domain :presentation :data All repositories exposed to the domain module All use cases exposed to the presentation module :app knows about all modules and up the DI framework

Slide 38

Slide 38 text

Domain Presentation Data Feature

Slide 39

Slide 39 text

• Avoid “N-Tier Architectures” • Architectural boundaries don’t run between modules but rather through them

Slide 40

Slide 40 text

:app :feature2 :base :feature1 :component

Slide 41

Slide 41 text

:module domain data ui model use case 
 repository activity fragment view model store repository local remote

Slide 42

Slide 42 text

Strategy #8 Dependency Injection

Slide 43

Slide 43 text

:app App Component Discovery Component Base Component Use Depend Build :discovery Build :base Use Use Build Use

Slide 44

Slide 44 text

@Singleton @Component(modules = [ BaseModule::class ]) interface BaseComponent { {…} fun router(): DiscoveryRouter companion object { // workaround val INSTANCE: BaseComponent = DaggerBaseComponent.create() } }

Slide 45

Slide 45 text

@ActivityScope @Component( modules = [ DiscoveryModule::class ], dependencies = [ BaseComponent::class ]) interface DiscoveryComponent : AndroidInjector { @Component.Builder abstract class Builder : AndroidInjector.Builder() { abstract fun plus(component: BaseComponent): Builder } }

Slide 46

Slide 46 text

DaggerDiscoveryComponent.builder() .plus(BaseComponent.INSTANCE) .create(this) .inject(this)

Slide 47

Slide 47 text

@Singleton @Component(modules = [ {…} BaseModule::class ]) interface AppComponent : AndroidInjector { fun inject(target: Activity) {…} }

Slide 48

Slide 48 text

Strategy #9 Only Talk to Your Friends

Slide 49

Slide 49 text

:app :discovery :base :search :instantapp

Slide 50

Slide 50 text

interface Router { companion object { const val BASE_URL = “https://example.com” } } interface DiscoveryRouter : Router { fun startAvailabilityFilterActivityForResult(activity: Activity, requestCode: Int, peopleCount: Int, date: LocalDate, time: LocalTime) object Params { const val PEOPLE_COUNT = “PEOPLE_COUNT" const val DATE = "DATE" const val TIME = "TIME" } }

Slide 51

Slide 51 text

@Reusable class UrlDiscoveryRouter @Inject constructor() : DiscoveryRouter { override fun startAvailabilityFilterActivityForResult(activity: Activity, requestCode: Int, peopleCount: Int, date: LocalDate, time: LocalTime) { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply { addCategory(Intent.CATEGORY_DEFAULT) addCategory(Intent.CATEGORY_BROWSABLE) setPackage(activity.packageName) putExtra(DiscoveryRouter.Params.PEOPLE_COUNT, peopleCount) putExtra(DiscoveryRouter.Params.DATE, date) putExtra(DiscoveryRouter.Params.TIME, time) } activity.startActivityForResult(fragment, intent, requestCode) } // companion object private const val URL = "${BASE_URL}/availability/filter" }

Slide 52

Slide 52 text

Slide 53

Slide 53 text

class SimpleActivity : AppCompatActivity() { @Inject lateinit var discoveryRouter: DiscoveryRouter fun onButtonClicked(…) { discoveryRouter.startAvailabilityFilterActivityForResult( activity = this, requestCode = requestCode, peopleCount = peopleCount, date = date, time = time) } }

Slide 54

Slide 54 text

Strategy #10 Delivery Small and Iterate

Slide 55

Slide 55 text

Start State A State B State C Minor change Minor change Minor change Quickly identify issues End Fix issues Rollback as last option New interaction

Slide 56

Slide 56 text

A Pragmatic Approach 1. Quality Directives 2. Scout the Codebase 3. Keep it Simple, Stupid 4. Don’t Talk to Strangers 5. Don’t Repeat Yourself 6. Small Interactions 7. Vertical Slice Architecture 8. Dependency Injection 9. Only Talk to Your Friends 10.Delivery Small and Iterate

Slide 57

Slide 57 text

Questions? We’re hiring! https://tinyurl.com/y4vfr9ud Marcello Galhardo Slides at https://tinyurl.com/y9zcf4u9