Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Droidcon 2020. Why We Need Clean Architecture

Igor Wojda
November 17, 2020

Droidcon 2020. Why We Need Clean Architecture

"You can’t anticipate everything that’s going to change in the software but you can try to build in enough flexibility that it’s likely to adapt to most changes"

Application architecture defines the way we develop, modularise, test, scale, and maintain our application. A good architecture allows us to deal with never-ending stream requirements by providing enough flexibility and solid protection against regression.

Igor will introduce the concept of Clean Architecture in the context of Android Development, discuss it's benefits, helps you understand how Clean Architecture fits into modern Android application development.

Igor Wojda

November 17, 2020
Tweet

More Decks by Igor Wojda

Other Decks in Technology

Transcript

  1. Why do we need
    Clean Architecture?
    1

    View full-size slide

  2. 2
    Hello!
    I AM IGOR WOJDA
    Android Developer Advocate, Vonage
    @igorwojda

    View full-size slide

  3. WHY DO WE NEED GOOD ARCHITECTURE?
    3

    View full-size slide

  4. “MVX ARCHITECTURE”
    4
    Controller
    Model
    Presenter
    Intent
    View
    ViewModel
    MVC
    MVP
    MVVM
    MVI
    X

    View full-size slide

  5. MVVM
    5
    View ViewModel Model

    View full-size slide

  6. MVVM + REPOSITORY
    6
    View ViewModel Repository
    DataSource
    DataSource

    View full-size slide


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

    View full-size slide

  8. WHY CLEAN ARCHITECTURE?
    ◦ Independent of libraries & frameworks
    ◦ Independent of UI
    ◦ Independent of data sources
    8
    Business
    Logic
    UI
    Libraries &
    Frameworks
    Data
    sources

    View full-size slide

  9. DEPENDENCY RULE
    9

    View full-size slide

  10. DEPENDENCY RULE
    10
    UI
    Data source
    ViewModel
    Repository
    Use case
    Entity

    View full-size slide

  11. UI
    Data source
    ViewModel
    Repository
    Use case
    Entity
    DEPENDENCY RULE
    11
    Dependency
    rule
    Data Flow

    View full-size slide

  12. UI
    Data source
    ViewModel
    Repository
    Use case
    Entity
    LAYERS
    12

    View full-size slide

  13. UI
    Data source
    ViewModel
    Repository
    Use case
    Entity
    Presentation Layer Domain Layer Data Layer
    LAYERS
    13
    Data Flow
    Dependency
    rule
    Dependency
    rule
    Data Flow

    View full-size slide

  14. ViewModel
    View
    Use case
    Entity
    Repository
    Network
    Data Source
    Disk
    Data Source
    14
    LAYERS
    Presentation Layer Domain Layer Data Layer

    View full-size slide

  15. DEPENDENCY INVERSION
    15
    UseCase Repository
    • Define interface
    • Class Repository implements this interface
    • Class Repository is injected into UseCase class
    UseCase Repository

    View full-size slide

  16. 16
    LAYERS
    ViewModel
    View
    Use case
    Entity
    Repository
    Network
    Data Source
    Disk
    Data Source
    Dependencies
    Presentation Layer Domain Layer Data Layer

    View full-size slide

  17. MODEL GOING WRONG
    17

    View full-size slide

  18. DATA MODEL
    18
    data class User(
    val id: String,
    val firstName: String,
    val lastName: String,
    val birthDate: String
    )

    View full-size slide

  19. ADD GSON ANNOTATIONS
    19
    data class User(
    @PrimaryKey("id")
    val id: String,
    @SerializedName("first_name")
    val firstName: String,
    @SerializedName("last_name")
    val lastName: String,
    @SerializedName("birth_date")
    val birthDate: String
    )

    View full-size slide

  20. ADD ROOM ANNOTATIONS
    20
    @Entity(tableName = "users")
    data class User(
    @PrimaryKey("id")
    @SerializedName("id")
    val id: String,
    @ColumnInfo(name = "first_name")
    @SerializedName("first_name")
    val firstName: String,
    @ColumnInfo(name = "last_name")
    @SerializedName("last_name")
    val lastName: String,
    @ColumnInfo(name = "birth_date")
    @SerializedName("birth_date")
    val birthDate: String
    )

    View full-size slide

  21. ADD GETTERS
    21
    @Entity(tableName = "users")
    data class User(
    @PrimaryKey("id")
    @SerializedName("id")
    val id: String,
    @ColumnInfo(name = "first_name")
    @SerializedName("first_name")
    val firstName: String,
    @ColumnInfo(name = "last_name")
    @SerializedName("last_name")
    val lastName: String,
    @ColumnInfo(name = "birth_date")
    @SerializedName("birth_date")
    val birthDate: String
    ) {
    val fullName = "$firstName $lastName”
    val isAdult = DateTime.parse(dateOfBirth)
    .plusYears(18)
    .isAfter(DateTime.now())
    }

    View full-size slide

  22. THE GOOD MODEL
    22
    @Entity(tableName = "users")
    data class User(
    @PrimaryKey("id")
    @SerializedName("id")
    val id: String,
    @ColumnInfo(name = "first_name")
    @SerializedName("first_name")
    val firstName: String,
    @ColumnInfo(name = "last_name")
    @SerializedName("last_name")
    val lastName: String,
    @ColumnInfo(name = "birth_date")
    @SerializedName("birth_date")
    val birthDate: String
    ) {
    val fullName = "$firstName $lastName”
    val isAdult = DateTime.parse(dateOfBirth)
    .plusYears(18)
    .isAfter(DateTime.now())
    }

    View full-size slide

  23. DATA MODELS
    24
    UserPresentationModel
    UserModel
    UserNetworkModel
    UserRoomModel
    UserNetworkMapper
    UserRoomMapper
    UserViewMapper
    Presentation Layer Domain Layer Data Layer

    View full-size slide

  24. USER NETWORK MODEL
    25
    Data Layer
    data class UserNetworkModel(
    @SerializedName("id")
    val id: String,
    @SerializedName("first_name")
    val firstName: String,
    @SerializedName("last_name")
    val lastName: String,
    @SerializedName("birth_date")
    val birthDate: String
    )

    View full-size slide

  25. USER ROOM MODEL
    26
    Data Layer
    @Entity(tableName = "users")
    data class UserRoomModel(
    @PrimaryKey("id")
    val id: String,
    @ColumnInfo(name = "first_name")
    val firstName: String,
    @ColumnInfo(name = "last_name")
    val lastName: String,
    @SerializedName("birth_date")
    val birthDate: String
    )

    View full-size slide

  26. USER MODEL
    27
    data class UserModel(
    val id: String,
    val firstName: String,
    val lastName: String,
    val birthDate: DateTime
    )
    Domain Layer

    View full-size slide

  27. USER PRESENTATION MODEL
    28
    Presentation Layer
    data class UserPresentationModel(
    val fullName: String,
    val birthDate: String
    )

    View full-size slide

  28. FIRST NAME REPRESENTATION
    29
    UserPresentationModel
    UserModel
    UserNetworkModel
    UserRoomModel
    UserNetworkMapper
    UserRoomMapper
    UserPresentationMapper
    Presentation Layer Domain Layer Data Layer
    “Igor"
    “Igor"
    “Igor"
    “Igor"

    View full-size slide

  29. DATE & TIME REPRESENTATION
    30
    UserPresentationModel
    UserModel
    UserNetworkModel
    UserRoomModel
    UserNetworkMapper
    UserRoomMapper
    UserPresentationMapper
    Presentation Layer Domain Layer Data Layer
    "20201107T170000Z"
    1604407243
    "11th Nov 2020”
    DateTime

    View full-size slide

  30. MAPPERS
    32
    UserPresentationModel
    UserModel
    UserNetworkModel
    UserRoomModel
    UserNetworkMapper
    UserRoomMapper
    UserPresentationMapper
    Presentation Layer Domain Layer Data Layer

    View full-size slide

  31. MAPPER
    33
    DataType1
    Mapper
    DataType2
    UserNetworkModel
    Mapper
    UserModel

    View full-size slide

  32. IMPLEMENTATION
    34

    View full-size slide

  33. MAPPER AS INFERFACE
    35
    interface Mapper {
    fun map(from: FROM): TO
    }
    class UserNetworkModelMapper : Mapper {
    override fun map(UserNetworkModel: UserNetworkModel) = UserModel(
    id = UserNetworkModel.id,
    firstName = UserNetworkModel.firstName,
    lastName = UserNetworkModel.lastName,
    birthDate = DateTime.parse(UserNetworkModel.birthDate)
    )
    }

    View full-size slide

  34. MAPPER AS INFERFACE
    36
    interface Mapper {
    fun map(from: FROM): TO
    }
    class UserNetworkModelMapper : Mapper {
    override fun map(UserNetworkModel: UserNetworkModel) = UserModel(
    id = UserNetworkModel.id,
    firstName = UserNetworkModel.firstName,
    lastName = UserNetworkModel.lastName,
    birthDate = DateTime.parse(UserNetworkModel.birthDate)
    )
    }

    View full-size slide

  35. MAPPER AS INFERFACE
    37
    interface Mapper {
    fun map(from: FROM): TO
    }
    class UserNetworkModelMapper : Mapper {
    override fun map(UserNetworkModel: UserNetworkModel) = UserModel(
    id = UserNetworkModel.id,
    firstName = UserNetworkModel.firstName,
    lastName = UserNetworkModel.lastName,
    birthDate = DateTime.parse(UserNetworkModel.birthDate)
    )
    }

    View full-size slide

  36. MAPPER AS EXTENSION
    38
    val usedModel = userNetworkModel.toDomainModel()
    fun UserNetworkModel.toDomainModel() = UserModel(
    id = this.id,
    firstName = this.firstName,
    lastName = this.lastName,
    birthDate = DateTime.parse(UserNetworkModel.birthDate)
    )

    View full-size slide

  37. ViewModel
    View
    Use case
    Entity
    Repository
    Network
    Data Source
    Disk
    Data Source
    39
    VIEW MODEL IMPLEMENTATION
    Presentation Layer Domain Layer Data Layer
    Abstractions

    View full-size slide

  38. ViewModel
    View
    Use case
    Entity
    Repository
    Network
    Data Source
    Disk
    Data Source
    40
    VIEW MODEL IMPLEMENTATION
    Presentation Layer Domain Layer Data Layer
    Abstractions

    View full-size slide

  39. VIEW MODEL IMPLEMENTATION
    41
    class UsersViewModel(val getUsersUseCase: GetUsersUseCase) : ViewModel {


    }

    View full-size slide

  40. VIEW MODEL IMPLEMENTATION
    42
    class UsersViewModel(val getUsersUseCase: GetUsersUseCase) : ViewModel {


    fun getUsers() {

    }

    }

    View full-size slide

  41. VIEW MODEL IMPLEMENTATION
    43
    class UsersViewModel(val getUsersUseCase: GetUsersUseCase) : ViewModel {


    fun getUsers() {
    viewModelScope.launch {

    getUsersUseCase.execute().also {
    }

    }

    }

    View full-size slide

  42. VIEW MODEL IMPLEMENTATION
    44
    class UsersViewModel(val getUsersUseCase: GetUsersUseCase) : ViewModel {


    fun getUsers() {
    viewModelScope.launch {

    getUsersUseCase.execute().also { result ->
    when (result) {
    is Success -> {
    // do something
    }
    is Error -> {
    // do something
    }
    }
    }

    }

    }

    View full-size slide

  43. VIEW MODEL IMPLEMENTATION
    45
    class UsersViewModel(val getUsersUseCase: GetUsersUseCase) : ViewModel {


    fun getUsers() {
    viewModelScope.launch {

    getUsersUseCase.execute().also { result ->
    when (result) {
    is Success -> {
    // do something
    }
    is Error -> {
    // do something
    }
    }
    }

    }

    }

    View full-size slide

  44. Presentation Layer Domain Layer Data Layer
    ViewModel
    View
    Use case
    Entity
    Repository
    Network
    Data Source
    Disk
    Data Source
    46
    USE CASE IMPLEMENTATION
    Abstractions

    View full-size slide

  45. USE CASE IMPLEMENTATION
    47
    class GetAdultUsersUseCase(val UsersRepository: UsersRepository) {
    }

    View full-size slide

  46. USE CASE IMPLEMENTATION
    48
    class GetAdultUsersUseCase(val UsersRepository: UsersRepository) {
    suspend fun execute(): List
    = UserRepository.getUsers()
    }

    View full-size slide

  47. USE CASE IMPLEMENTATION
    49
    class GetAdultUsersUseCase(val UsersRepository: UsersRepository) {
    suspend fun execute(): List
    = UserRepository.getUsers()
    .filter { it.adult }
    }

    View full-size slide

  48. USE CASE IMPLEMENTATION
    50
    class GetAdultUsersUseCase(val UsersRepository: UsersRepository) {
    suspend fun execute(): List
    = UserRepository.getUsers()
    .filter { it.adult }
    }

    View full-size slide

  49. Presentation Layer Domain Layer Data Layer
    ViewModel
    View
    Use case
    Entity
    Repository
    Network
    Data Source
    Disk
    Data Source
    51
    USE CASE IMPLEMENTATION
    Abstractions

    View full-size slide

  50. ENTITY IMPLEMENTATION
    52
    data class UserModel(
    val id: String,
    val firstName: String,
    val lastName: String,
    val birthDate: DateTime
    )

    View full-size slide

  51. Presentation Layer Domain Layer Data Layer
    ViewModel
    View
    Use case
    Entity
    Repository
    Network
    Data Source
    Disk
    Data Source
    53
    USE CASE IMPLEMENTATION
    Abstractions

    View full-size slide

  52. ABSTRACIONS IMPLEMENTATION
    54
    interface UsersRepository() {
    fun getUsers(): List
    }

    View full-size slide

  53. Presentation Layer Domain Layer Data Layer
    ViewModel
    View
    Use case
    Entity
    Repository
    Network
    Data Source
    Disk
    Data Source
    55
    REPOSITORY IMPLEMENTATION
    Abstractions

    View full-size slide

  54. class UsersRepositoryImpl(
    private val usersRetrofitService: UsersRetrofitService
    ) : UsersRepository {
    }
    REPOSITORY IMPLEMENTATION
    56
    UserRepository UserRepositoryImpl

    View full-size slide

  55. class UsersRepositoryImpl(
    private val usersRetrofitService: UsersRetrofitService
    ) : UsersRepository {
    override suspend fun getUsers() =
    usersRetrofitService.getUsersAsync()
    }
    REPOSITORY IMPLEMENTATION
    57

    View full-size slide

  56. class UsersRepositoryImpl(
    private val usersRetrofitService: UsersRetrofitService
    ) : UsersRepository {
    override suspend fun getUsers() =
    usersRetrofitService.getUsersAsync()
    .map { it.toDomainModel() }
    }
    REPOSITORY IMPLEMENTATION
    58

    View full-size slide

  57. class UsersRepositoryImpl(
    private val usersRetrofitService: UsersRetrofitService
    ) : UsersRepository {
    override suspend fun getUsers() =
    usersRetrofitService.getUsersAsync()
    .map { it.toDomainModel() }
    }
    REPOSITORY IMPLEMENTATION
    59

    View full-size slide

  58. LAYERS IMPLEMENTATION
    60
    Layers in a single module Layers inside the feature modules
    Layer per module

    View full-size slide

  59. 61
    DATA FLOW
    View ViewModel Use case Repository
    Network
    Data Source
    SQLite
    Data Source
    Mapper

    View full-size slide

  60. 62
    MODULARITY
    Presentation Layer Domain Layer Data Layer
    ViewModel
    Business
    Logic
    View
    GridView
    Recycler
    View
    TabLayout
    BottomBar
    User
    Repository
    Tracking
    Repository
    SQLite
    Volley
    Firebase
    Retrofit
    MixPanel
    Room

    View full-size slide

  61. 63
    TESTING
    Presentation Layer Domain Layer Data Layer
    Use Case
    Repository
    Entity
    ViewModel
    View

    View full-size slide

  62. 64
    TESTING
    Presentation Layer Domain Layer Data Layer
    ViewModel Use Case
    View
    Repository
    Entity
    Mapper
    Mock
    Room Data Source
    Mock
    Network Data Source
    Mock
    Repository
    Mock
    ViewModel
    Mock
    Mapper
    Mock
    Mapper
    Mock
    Mapper
    Mock
    Entity Entity
    Entity
    Use Case
    Mock
    Mapper
    Mock

    View full-size slide

  63. 65
    PROS AND CONS
    • Great for long living projects
    • Great for big teams
    • Maintainability
    • Scalability
    • Parallel teams
    • Maintainable
    • Testable
    Cons
    Pros
    • Boilerplate code
    • Not suitable for all projects
    • Multiple ways to implement

    View full-size slide

  64. 66
    REFERENCES
    Articles
    • The Clean Architecture https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-
    architecture.html
    • Multiple ways of defining Clean Architecture layers https://proandroiddev.com/
    multiple-ways-of-defining-clean-architecture-layers-bbb70afa5d4a
    Android projects
    • https://github.com/igorwojda/android-showcase
    • https://github.com/android10/Android-CleanArchitecture-Kotlin
    • https://github.com/bufferapp/android-clean-architecture-boilerplate
    • https://github.com/bufferapp/clean-architecture-components-boilerplate
    Talks
    • https://youtu.be/Nsjsiz2A9mg
    • https://youtu.be/3Mq5newPdck
    • https://youtu.be/-sEmrJOk1uI
    • https://youtu.be/su34EYeQ90E

    View full-size slide

  65. 67
    ANY QUESTIONS?
    Find me on twitter
    @IgorWojda
    Thanks!

    View full-size slide

  66. 68
    Special thanks to SlidesCarnival who made and released these
    awesome presentation template for free.
    CREDITS

    View full-size slide