Slide 1

Slide 1 text

Why do we need Clean Architecture? Igor Wojda @igorwojda

Slide 2

Slide 2 text

About me

Slide 3

Slide 3 text

Why do we need good architecture? 1519 1520 1521 1522 1523 1524

Slide 4

Slide 4 text

MVP View Presenter Model

Slide 5

Slide 5 text

MVP + Repository View Presenter Repository DataSource DataSource

Slide 6

Slide 6 text

“ We should think about our applications as group of use cases that describe the intent of the application and group of plugins that give those uses cases access to outside word. Uncle Bob

Slide 7

Slide 7 text

Why Clean Architecture? ◦ Independent of libraries & frameworks ◦ Independent of UI ◦ Independent of data sources Business Logic UI Frameworks Data sources

Slide 8

Slide 8 text

UI Data source Presenter Repository Use case Dependency rule Entity

Slide 9

Slide 9 text

Dependency rule UI Data source Presenter Repository Use case Entity Dependency rule Data Flow

Slide 10

Slide 10 text

UI Data source Presenter Repository Use case Entity Layers

Slide 11

Slide 11 text

Presentation Layer Domain Layer Data Layer Layers

Slide 12

Slide 12 text

Presentation Layer Data Layer Presenter View Use case Entity Repository Network Data Source Disk Data Source Layers Domain Layer Dependencies

Slide 13

Slide 13 text

Dependency inversion UseCase Repository • Define interface • Class Repository implements this interface • Class Repository is injected into class UseCase UseCase Repository

Slide 14

Slide 14 text

Presentation Layer Domain Layer Data Layer Presenter View Use case Repository Network Data Source Disk Data Source Layers Abstractions Entity

Slide 15

Slide 15 text

Data model data class PatientModel( val id: String, val firstName: String, val lastName: String, val dateOfBirth: String )

Slide 16

Slide 16 text

Data model (GSON) data class PatientModel( @SerializedName("id") val id: String, @SerializedName("first_name") val firstName: String, @SerializedName("last_name") val lastName: String, @SerializedName("date_of_birth") val dateOfBirth: String )

Slide 17

Slide 17 text

Data model (GSON + Room) @Entity(tableName = "patients") data class PatientModel( @SerializedName("id") @PrimaryKey val id: String, @SerializedName("first_name") @ColumnInfo(name = "first_name") val firstName: String, @SerializedName("last_name") @ColumnInfo(name = "last_name") val lastName: String, @SerializedName("date_of_birth") @ColumnInfo(name = "date_of_birth") val dateOfBirth: String )

Slide 18

Slide 18 text

Data model (GSON + Room + Getters) @Entity(tableName = "patients") data class PatientModel( @SerializedName("id") @PrimaryKey val id: String, @SerializedName("first_name") @ColumnInfo(name = "first_name") val firstName: String, @SerializedName("last_name") @ColumnInfo(name = "last_name") val lastName: String, @SerializedName("date_of_birth") @ColumnInfo(name = "date_of_birth") val dateOfBirth: String ) { private val UNDER_AGE_LIMIT = 16 val fullName = "$firstName $lastName" val adult = DateTime.parse(dateOfBirth) .plusYears(UNDER_AGE_LIMIT) .isAfter(DateTime.now()) }

Slide 19

Slide 19 text

Data models Presentation Layer Domain Layer Data Layer PatientPresentation Model Patient PatientNetworkModel PatientRoomModel PatientNetworkMapper PatientRoomMapper PatientViewMapper

Slide 20

Slide 20 text

Separate data model data class PatientNetworkModel( @SerializedName("id") val id: String, @SerializedName("first_name") val firstName: String, @SerializedName("last_name") val lastName: String, @SerializedName("date_of_birth") val dateOfBirth: String ) Data Layer

Slide 21

Slide 21 text

Separate data model Data Layer @Entity(tableName = "patients") data class PatientRoomModel( @PrimaryKey val id: String, @ColumnInfo(name = "first_name") val firstName: String, @ColumnInfo(name = "last_name") val lastName: String, @ColumnInfo(name = "date_of_birth") val dateOfBirth: String )

Slide 22

Slide 22 text

Separate data model Domain Layer data class Patient( val id: String, val firstName: String, val lastName: String, val dateOfBirth: DateTime )

Slide 23

Slide 23 text

Separate data model data class PatientPresentationModel( val fullName: String, val adult: Boolean, val dateOfBirth: String ) Presentation Layer

Slide 24

Slide 24 text

Data representations Presentation Layer Domain Layer Data Layer

Slide 25

Slide 25 text

Data representations Presentation Layer Domain Layer Data Layer "20171026T170000Z" DateTime "26th Oct 2017" 1509037200

Slide 26

Slide 26 text

Mapper interface Mapper { fun map(from: FROM): TO } class PatientNetworkModelMapper : Mapper { override fun map(patientNetworkModel: PatientNetworkModel) = Patient( id = patientNetworkModel.id, firstName = patientNetworkModel.firstName, lastName = patientNetworkModel.lastName, dateOfBirth = DateTime.parse(patientNetworkModel.dateOfBirth) ) }

Slide 27

Slide 27 text

Mapper interface Mapper { fun map(from: FROM): TO } class PatientNetworkModelMapper : Mapper { override fun map(patientNetworkModel: PatientNetworkModel) = Patient( id = patientNetworkModel.id, firstName = patientNetworkModel.firstName, lastName = patientNetworkModel.lastName, dateOfBirth = DateTime.parse(patientNetworkModel.dateOfBirth) ) }

Slide 28

Slide 28 text

Mapper interface Mapper { fun map(from: FROM): TO } class PatientNetworkModelMapper : Mapper { override fun map(patientNetworkModel: PatientNetworkModel) = Patient( id = patientNetworkModel.id, firstName = patientNetworkModel.firstName, lastName = patientNetworkModel.lastName, dateOfBirth = DateTime.parse(patientNetworkModel.dateOfBirth) ) }

Slide 29

Slide 29 text

Presentation Layer Domain Layer Data Layer Presenter View Use case Entity Repository Network Data Source Disk Data Source Implementations Abstractions

Slide 30

Slide 30 text

Presenter class PatientsPresenter @Inject constructor( val getPatientsUseCase: PatientsUseCase, val mapper: PatientPresentationMapper) : BasePresenter, PatientsContract.Presenter { override fun onAttachView() { getPatientsUseCase.execute(PatientSubscriber()) } inner class PatientSubscriber : DisposableSingleObserver>() { override fun onSuccess(patients: List) { when { patients.isEmpty -> view?.showEmptyState() else -> view?.showPatients(Patients.map { patientMapper::map }) } } override fun onError(exception: Throwable) { view?.showErrorState() } //… } }

Slide 31

Slide 31 text

Presenter class PatientsPresenter @Inject constructor( val getPatientsUseCase: PatientsUseCase, val mapper: PatientPresentationMapper) : BasePresenter, PatientsContract.Presenter { override fun onAttachView() { getPatientsUseCase.execute(PatientSubscriber()) } inner class PatientSubscriber : DisposableSingleObserver>() { override fun onSuccess(patients: List) { when { patients.isEmpty -> view?.showEmptyState() else -> view?.showPatients(mapper.map { patientMapper::map }) } } override fun onError(exception: Throwable) { view?.showErrorState() } //… } }

Slide 32

Slide 32 text

Presenter class PatientsPresenter @Inject constructor( val getPatientsUseCase: PatientsUseCase, val mapper: PatientPresentationMapper) : BasePresenter, PatientsContract.Presenter { override fun onAttachView() { getPatientsUseCase.execute(PatientSubscriber()) } inner class PatientSubscriber : DisposableSingleObserver>() { override fun onSuccess(patients: List) { when { patients.isEmpty -> view?.showEmptyState() else -> view?.showPatients(mapper.map { patientMapper::map }) } } override fun onError(exception: Throwable) { view?.showErrorState() } //… } }

Slide 33

Slide 33 text

Presenter class PatientsPresenter @Inject constructor( val getPatientsUseCase: PatientsUseCase, val mapper: PatientPresentationMapper) : BasePresenter, PatientsContract.Presenter { override fun onAttachView() { getPatientsUseCase.execute(PatientSubscriber()) } inner class PatientSubscriber : DisposableSingleObserver>() { override fun onSuccess(patients: List) { when { patients.isEmpty -> view?.showEmptyState() else -> view?.showPatients(mapper.map { patientMapper::map }) } } override fun onError(exception: Throwable) { view?.showErrorState() } //… } }

Slide 34

Slide 34 text

Presenter class PatientsPresenter @Inject constructor( val getPatientsUseCase: PatientsUseCase, val mapper: PatientPresentationMapper) : BasePresenter, PatientsContract.Presenter { override fun onAttachView() { getPatientsUseCase.execute(PatientSubscriber()) } inner class PatientSubscriber : DisposableSingleObserver>() { override fun onSuccess(patients: List) { when { patients.isEmpty -> view?.showEmptyState() else -> view?.showPatients(mapper.map { patientMapper::map }) } } override fun onError(exception: Throwable) { view?.showErrorState() } //… } }

Slide 35

Slide 35 text

Presentation Layer Domain Layer Data Layer Presenter View Use case Entity Repository Network Data Source Disk Data Source Layers Abstractions

Slide 36

Slide 36 text

Use case (Interactor) class PatientsUseCase @Inject constructor(val patientRepository: PatientRepository, val schedulers: Schedulers) { fun execute(): Single> = patientRepository.getPatients() .observeOn(schedulers.main) .subscribeOn(schedulers.io) .filter { it.adult } }

Slide 37

Slide 37 text

Use case (Interactor) class PatientsUseCase @Inject constructor(val patientRepository: PatientRepository, val schedulers: Schedulers) { fun execute(): Single> = patientRepository.getPatients() .observeOn(schedulers.main) .subscribeOn(schedulers.io) .filter { it.adult } }

Slide 38

Slide 38 text

Use case (Interactor) class PatientsUseCase @Inject constructor(val patientRepository: PatientRepository, val schedulers: Schedulers) { fun execute(): Single> = patientRepository.getPatients() .observeOn(schedulers.main) .subscribeOn(schedulers.io) .filter { it.adult } }

Slide 39

Slide 39 text

Presentation Layer Domain Layer Data Layer Presenter View Use case Entity Repository Network Data Source Disk Data Source Layers Abstractions

Slide 40

Slide 40 text

class PatientDataRepository @Inject constructor(val diskDataStore: PatientDiskDataStore, val networkDataStore: PatientNetworkDataStore) : PatientRepository { val networkDataStoreWithSave = networkDataStore.doOnSuccess { saveToDisk(it) } fun getPatients(): Single> = Single.concat(diskDataStore, networkDataStoreWithSave) .takeFirst() fun saveToDisk() { /* … */ } } Repository

Slide 41

Slide 41 text

class PatientDataRepository @Inject constructor(val diskDataStore: PatientDiskDataStore, val networkDataStore: PatientNetworkDataStore) : PatientRepository { val networkDataStoreWithSave = networkDataStore.doOnSuccess { saveToDisk(it) } fun getPatients(): Single> = Single.concat(diskDataStore, networkDataStoreWithSave) .takeFirst() fun saveToDisk() { /* … */ } } Repository PatientRepository PatientDataRepository: PatientRepository

Slide 42

Slide 42 text

class PatientDataRepository @Inject constructor(val diskDataStore: PatientDiskDataStore, val networkDataStore: PatientNetworkDataStore) : PatientRepository { val networkDataStoreWithSave = networkDataStore.doOnSuccess { saveToDisk(it) } fun getPatients(): Single> = Single.concat(diskDataStore, networkDataStoreWithSave) .takeFirst() fun saveToDisk() { /* … */ } } Repository

Slide 43

Slide 43 text

class PatientDataRepository @Inject constructor(val diskDataStore: PatientDiskDataStore, val networkDataStore: PatientNetworkDataStore) : PatientRepository { val networkDataStoreWithSave = networkDataStore.doOnSuccess { saveToDisk(it) } fun getPatients(): Single> = Single.concat(diskDataStore, networkDataStoreWithSave) .takeFirst() fun saveToDisk() { /* … */ } } Repository

Slide 44

Slide 44 text

Data flow View Presenter Use case Repository Network Data Source SQLite Data Source Entity

Slide 45

Slide 45 text

Modularity Presentation Layer Domain Layer Data Layer Presenter Business Logic View GridView Recycler View TabLayout BottomBar Patient Repository Tracking Repository SQLite Volley Firebase Firebase Room Retrofit MixPanel

Slide 46

Slide 46 text

Testing Presentation Layer Domain Layer Data Layer Presenter Use case View Repository Network Data Source Entity Room Data Source

Slide 47

Slide 47 text

Testing Presentation Layer Domain Layer Data Layer Mapper Mock RetrofitService Mock Mapper Mock Mock SQLite Data Source Mock Data Source Mock Repository Mock Presenter Mock Mapper Mock Mapper Mock Mapper Mock Entity Entity Entity View Mock Use Case Mock Mapper Mock

Slide 48

Slide 48 text

Pros and cons Boilerplate code Not suitable for all projects Multiple ways to implement Independent of Frameworks and tools Well structured Maintainable Modular Testable Great for big teams Issues Benefits

Slide 49

Slide 49 text

References Articles https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html Talks https://youtu.be/Nsjsiz2A9mg https://youtu.be/3Mq5newPdck https://youtu.be/-sEmrJOk1uI https://youtu.be/su34EYeQ90E Android projects https://github.com/bufferapp/android-clean-architecture-boilerplate https://github.com/bufferapp/clean-architecture-components-boilerplate https://github.com/android10/Android-CleanArchitecture-Kotlin

Slide 50

Slide 50 text

Thanks! ANY QUESTIONS? @igorwojda [email protected]

Slide 51

Slide 51 text

Special thanks to SlidesCarnival who made and released these awesome presentation template for free. Credits