Why do we need Clean Architecture

E481624463bdb1c97c46e2155408acb4?s=47 Igor Wojda
October 26, 2017

Why do we need Clean Architecture

Recording is available here (free registration required)
http://uk.droidcon.com/skillscasts/11002-why-do-we-need-clean-architecture

E481624463bdb1c97c46e2155408acb4?s=128

Igor Wojda

October 26, 2017
Tweet

Transcript

  1. Why do we need Clean Architecture? Igor Wojda @igorwojda

  2. About me

  3. Why do we need good architecture? 1519 1520 1521 1522

    1523 1524
  4. MVP View Presenter Model

  5. MVP + Repository View Presenter Repository DataSource DataSource

  6. “ 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
  7. Why Clean Architecture? ◦ Independent of libraries & frameworks ◦

    Independent of UI ◦ Independent of data sources Business Logic UI Frameworks Data sources
  8. UI Data source Presenter Repository Use case Dependency rule Entity

  9. Dependency rule UI Data source Presenter Repository Use case Entity

    Dependency rule Data Flow
  10. UI Data source Presenter Repository Use case Entity Layers

  11. Presentation Layer Domain Layer Data Layer Layers

  12. Presentation Layer Data Layer Presenter View Use case Entity Repository

    Network Data Source Disk Data Source Layers Domain Layer Dependencies
  13. Dependency inversion UseCase Repository • Define interface • Class Repository

    implements this interface • Class Repository is injected into class UseCase UseCase Repository
  14. Presentation Layer Domain Layer Data Layer Presenter View Use case

    Repository Network Data Source Disk Data Source Layers Abstractions Entity
  15. Data model data class PatientModel( val id: String, val firstName:

    String, val lastName: String, val dateOfBirth: String )
  16. 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 )
  17. 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 )
  18. 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()) }
  19. Data models Presentation Layer Domain Layer Data Layer PatientPresentation Model

    Patient PatientNetworkModel PatientRoomModel PatientNetworkMapper PatientRoomMapper PatientViewMapper
  20. 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
  21. 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 )
  22. Separate data model Domain Layer data class Patient( val id:

    String, val firstName: String, val lastName: String, val dateOfBirth: DateTime )
  23. Separate data model data class PatientPresentationModel( val fullName: String, val

    adult: Boolean, val dateOfBirth: String ) Presentation Layer
  24. Data representations Presentation Layer Domain Layer Data Layer

  25. Data representations Presentation Layer Domain Layer Data Layer "20171026T170000Z" DateTime

    "26th Oct 2017" 1509037200
  26. Mapper interface Mapper<in FROM, out TO> { fun map(from: FROM):

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

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

    TO } class PatientNetworkModelMapper : Mapper<PatientNetworkModel, Patient> { override fun map(patientNetworkModel: PatientNetworkModel) = Patient( id = patientNetworkModel.id, firstName = patientNetworkModel.firstName, lastName = patientNetworkModel.lastName, dateOfBirth = DateTime.parse(patientNetworkModel.dateOfBirth) ) }
  29. Presentation Layer Domain Layer Data Layer Presenter View Use case

    Entity Repository Network Data Source Disk Data Source Implementations Abstractions
  30. Presenter class PatientsPresenter @Inject constructor( val getPatientsUseCase: PatientsUseCase, val mapper:

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

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

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

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

    PatientPresentationMapper) : BasePresenter<PatientsContract.View>, PatientsContract.Presenter { override fun onAttachView() { getPatientsUseCase.execute(PatientSubscriber()) } inner class PatientSubscriber : DisposableSingleObserver<List<Patient>>() { override fun onSuccess(patients: List<Patient>) { when { patients.isEmpty -> view?.showEmptyState() else -> view?.showPatients(mapper.map { patientMapper::map }) } } override fun onError(exception: Throwable) { view?.showErrorState() } //… } }
  35. Presentation Layer Domain Layer Data Layer Presenter View Use case

    Entity Repository Network Data Source Disk Data Source Layers Abstractions
  36. Use case (Interactor) class PatientsUseCase @Inject constructor(val patientRepository: PatientRepository, val

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

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

    schedulers: Schedulers) { fun execute(): Single<List<Patient>> = patientRepository.getPatients() .observeOn(schedulers.main) .subscribeOn(schedulers.io) .filter { it.adult } }
  39. Presentation Layer Domain Layer Data Layer Presenter View Use case

    Entity Repository Network Data Source Disk Data Source Layers Abstractions
  40. class PatientDataRepository @Inject constructor(val diskDataStore: PatientDiskDataStore, val networkDataStore: PatientNetworkDataStore) :

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

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

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

    PatientRepository { val networkDataStoreWithSave = networkDataStore.doOnSuccess { saveToDisk(it) } fun getPatients(): Single<List<Patient>> = Single.concat(diskDataStore, networkDataStoreWithSave) .takeFirst() fun saveToDisk() { /* … */ } } Repository
  44. Data flow View Presenter Use case Repository Network Data Source

    SQLite Data Source Entity
  45. 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
  46. Testing Presentation Layer Domain Layer Data Layer Presenter Use case

    View Repository Network Data Source Entity Room Data Source
  47. 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
  48. 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
  49. 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
  50. Thanks! ANY QUESTIONS? @igorwojda igor.wojda@gmail.com

  51. Special thanks to SlidesCarnival who made and released these awesome

    presentation template for free. Credits