$30 off During Our Annual Pro Sale. View Details »

Why do we need Clean Architecture

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

Igor Wojda

October 26, 2017
Tweet

More Decks by Igor Wojda

Other Decks in Programming

Transcript

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

    View Slide

  2. About me

    View Slide

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

    View Slide

  4. MVP
    View Presenter Model

    View Slide

  5. MVP + Repository
    View Presenter Repository
    DataSource
    DataSource

    View Slide


  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  10. UI
    Data source
    Presenter
    Repository
    Use case
    Entity
    Layers

    View Slide

  11. Presentation Layer Domain Layer Data Layer
    Layers

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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
    )

    View Slide

  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
    )

    View Slide

  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())
    }

    View Slide

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

    View Slide

  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

    View Slide

  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
    )

    View Slide

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

    View Slide

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

    View Slide

  24. Data representations
    Presentation Layer Domain Layer Data Layer

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  30. 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()
    }
    //…
    }
    }

    View Slide

  31. 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()
    }
    //…
    }
    }

    View Slide

  32. 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()
    }
    //…
    }
    }

    View Slide

  33. 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()
    }
    //…
    }
    }

    View Slide

  34. 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()
    }
    //…
    }
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  50. Thanks!
    ANY QUESTIONS?
    @igorwojda
    [email protected]

    View Slide

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

    View Slide