Clean Architecture

Clean Architecture

A practical guide to making your code more readable, testable, and maintainable using clean architecture.

B6ea8bb06748e123565b6a997febbac8?s=128

Michael Pardo

April 30, 2016
Tweet

Transcript

  1. Clean Architecture

  2. What is Architecture?

  3. • MVP • Realm • RxJava • Dagger • Retrofit

    • Gson
  4. • MVP • Realm • RxJava • Dagger • Retrofit

    • Gson NO!
  5. Tools • MVP • Realm • RxJava • Dagger •

    Retrofit • Gson
  6. Screaming Architecture

  7. None
  8. None
  9. Architecture is about intent

  10. Architecture is about use cases

  11. Entities Use Cases Controllers Gateways Presenters Devices Web DB UI

    External Interfaces Enterprise Business Rules Application Business Rules Interface Adapters Frameworks & Drivers Presenter Controller Use Case Input Port Use Case Interactor Use Case Output Port Flow of Control
  12. Entities Use Cases Controllers Gateways Presenters Devices Web DB UI

    External Interfaces Enterprise Business Rules Application Business Rules Interface Adapters Frameworks & Drivers Presenter Controller Use Case Input Port Use Case Interactor Use Case Output Port Flow of Control
  13. Use Cases Controllers Gateways Presenters Devices Web DB UI External

    Interfaces Enterprise Business Rules Application Business Rules Interface Adapters Frameworks & Drivers Presenter Controller Use Case Input Port Use Case Interactor Use Case Output Port Flow of Control
  14. Controllers Gateways Presenters Devices Web DB UI External Interfaces Enterprise

    Business Rules Application Business Rules Interface Adapters Frameworks & Drivers Presenter Controller Use Case Input Port Use Case Interactor Use Case Output Port Flow of Control
  15. Devices Web DB UI External Interfaces Enterprise Business Rules Application

    Business Rules Interface Adapters Frameworks & Drivers Presenter Controller Use Case Input Port Use Case Interactor Use Case Output Port Flow of Control
  16. POLICIES The Dependency Rule MECHANISMS

  17. POLICIES The Dependency Rule MECHANISMS

  18. POLICIES The Dependency Rule MECHANISMS

  19. POLICIES The Dependency Rule MECHANISMS

  20. POLICIES The Dependency Rule MECHANISMS

  21. Independent of Frameworks

  22. Testable

  23. Independent of UI

  24. Independent of Database

  25. Independent of External Agency

  26. Decoupled

  27. A Clean App

  28. github.com/pardom/CleanNews

  29. Entities Use cases Data sources, Presentation models Delivery mechanisms, databases

    Core App Presentation Data Android Desktop CleanNews ├── android ├── app ├── core ├── data ├── desktop └── presentation
  30. Entities Use cases Data sources, Presentation models Delivery mechanisms, databases

    Core App Presentation Data Android Desktop CleanNews ├── android ├── app ├── core ├── data ├── desktop └── presentation
  31. Entities Use cases Data sources, Presentation models Delivery mechanisms, databases

    App Presentation Data Android Desktop CleanNews ├── android ├── app ├── core ├── data ├── desktop └── presentation
  32. Entities Use cases Data sources, Presentation models Delivery mechanisms, databases

    Presentation Data Android Desktop CleanNews ├── android ├── app ├── core ├── data ├── desktop └── presentation
  33. Entities Use cases Data sources, Presentation models Delivery mechanisms, databases

    Android Desktop CleanNews ├── android ├── app ├── core ├── data ├── desktop └── presentation
  34. app/src/main/kotlin/clean/news/app/
 ├── item
 │ ├── GetChildren
 │ ├── GetItemById
 │

    ├── GetItemsByListType
 │ └── SaveItem
 └── user
 ├── GetUserById
 └── SaveUser
  35. app/src/main/kotlin/clean/news/app/
 ├── item
 │ ├── GetChildren
 │ ├── GetItemById
 │

    ├── GetItemsByListType
 │ └── SaveItem
 └── user
 ├── GetUserById
 └── SaveUser
  36. Entities

  37. “An entity is something that exists as itself, as a

    subject or as an object, actually or potentially, concretely or abstractly, physically or not.”
  38. apply plugin: 'java'
 
 dependencies { 
 } Core

  39. core/src/main/kotlin/clean/news/core/entity/
 ├── Item.kt
 └── User.kt

  40. data class Item(
 val id: Long,
 val type: Type,
 val

    by: String?,
 val time: Date,
 val text: String?,
 val url: String?,
 val title: String?) {
 
 enum class Type(val canComment: Boolean) {
 JOB(false), STORY(true), COMMENT(true), POLL(true), POLLOPT(true), NONE(false)
 }
 }
  41. data class Item(
 val id: Long,
 val type: Type,
 val

    by: String?,
 val time: Date,
 val text: String?,
 val url: String?,
 val title: String?) {
 
 enum class Type(val canComment: Boolean) {
 JOB(false), STORY(true), COMMENT(true), POLL(true), POLLOPT(true), NONE(false)
 }
 }
  42. data class User(
 val id: String,
 val delay: Int,
 val

    created: Date,
 val karma: Int,
 val about: String,
 val submitted: List<Long>
 )
  43. Entities • Contain enterprise wide business rules • Encapsulate high-level

    rules • Unaffected by external layers
  44. Use Cases

  45. “A use case is a list of actions or event

    steps, typically defining the interactions between a role and a system, to achieve a goal.”
  46. Title (goal) Primary Actor: …
 Scope: …
 Level: … (Story):

    the body of the use case is simply a paragraph or two of text, informally describing what happens.
  47. Retrieve story details Primary Actor: Reader
 Scope: A Hacker News

    client
 Level: User goal The reader invokes the client to retrieve the details for a story. This includes the children of a story, namely the comments.
  48. apply plugin: 'java'
 
 dependencies {
 compile project(':core')
 } Core

    App
  49. Entities Use Cases Controllers Gateways Presenters Devices Web DB UI

    External Interfaces Enterprise Business Rules Application Business Rules Interface Adapters Frameworks & Drivers Presenter Controller Use Case Input Port Use Case Interactor Use Case Output Port Flow of Control
  50. Presenter Controller Use Case Input Port Use Case Interactor Use

    Case Output Port Flow of Control
  51. Request Model Response Model Use Case Boundary <I> Boundary Presentation

    Model
  52. Request Model Response Model Use Case Boundary <I> Boundary Presentation

    Model
  53. Request Model Response Model Use Case Boundary <I> Boundary Presentation

    Model
  54. Request Model Response Model Use Case Boundary <I> Boundary Presentation

    Model
  55. Request Model Response Model Use Case Boundary <I> Boundary Presentation

    Model
  56. Request Model Response Model Use Case Boundary <I> Boundary Presentation

    Model
  57. Request Model Response Model Use Case Boundary <I> Boundary Presentation

    Model
  58. Request Model Response Model Use Case Boundary <I> Boundary Presentation

    Model
  59. Request Model Response Model Use Case Boundary <I> Boundary Presentation

    Model
  60. Request Model Response Model Use Case Boundary <I> Boundary Presentation

    Model
  61. Request Model Response Model Use Case Boundary <I> Boundary Presentation

    Model
  62. Request Model Response Model Use Case Boundary <I> Boundary Presentation

    Model
  63. Request Model Response Model Use Case Boundary <I> Boundary Presentation

    Model
  64. Request Model Response Model Use Case Boundary <I> Boundary Presentation

    Model
  65. Request Model Response Model Use Case Boundary <I> Boundary Presentation

    Model
  66. interface UseCase<I : Request, O : Response> {
 
 fun

    execute(request: I): Observable<O>
 
 interface Request
 
 interface Response
 
 }
  67. interface UseCase<I : Request, O : Response> {
 
 fun

    execute(request: I): Observable<O>
 
 interface Request
 
 interface Response
 
 }
  68. interface UseCase<I : Request, O : Response> {
 
 fun

    execute(request: I): Observable<O>
 
 interface Request
 
 interface Response
 
 }
  69. interface UseCase<I : Request, O : Response> {
 
 fun

    execute(request: I): Observable<O>
 
 interface Request
 
 interface Response
 
 }
  70. interface UseCase<I : Request, O : Response> {
 
 fun

    execute(request: I): Observable<O>
 
 interface Request
 
 interface Response
 
 }
  71. interface UseCase<I : Request, O : Response> {
 
 fun

    execute(request: I): Observable<O>
 
 interface Request
 
 interface Response
 
 }
  72. interface UseCase<I : Request, O : Response> {
 
 fun

    execute(request: I): Observable<O>
 
 interface Request
 
 interface Response
 
 }
  73. interface UseCase<I : Request, O : Response> {
 
 fun

    execute(request: I): Observable<O>
 
 interface Request
 
 interface Response
 
 }
  74. interface UseCase<R> {.
 
 fun execute(): Observable<R>
 
 }.

  75. interface UseCase1<A, R> {.
 
 fun execute(arg1: A): Observable<R>
 


    }.
  76. interface UseCase2<A, B, R> {.
 
 fun execute(arg1: A, arg2:

    B): Observable<R>
 
 }.
  77. interface UseCase3<A, B, C, R> {.
 
 fun execute(arg1: A,

    arg2: B, arg3: C): Observable<R>
 
 }.
  78. app/src/main/kotlin/clean/news/app/
 └── usecase
 ├── item
 │ ├── GetChildren.kt
 │ ├──

    GetItemById.kt
 │ ├── GetItemsByListType.kt
 │ └── SaveItem.kt
 └── user
 ├── GetUserById.kt
 └── SaveUser.kt
  79. GetChildren.Request GetChildren.Response GetChildren ItemNetworkDataSource ItemRetrofitDataSource ItemDetailsViewModel

  80. GetChildren.Request GetChildren.Response GetChildren ItemNetworkDataSource ItemRetrofitDataSource ItemDetailsViewModel

  81. GetChildren.Request GetChildren.Response GetChildren ItemNetworkDataSource ItemRetrofitDataSource ItemDetailsViewModel

  82. GetChildren.Request GetChildren.Response GetChildren ItemNetworkDataSource ItemRetrofitDataSource ItemDetailsViewModel

  83. GetChildren.Request GetChildren.Response GetChildren ItemNetworkDataSource ItemRetrofitDataSource ItemDetailsViewModel

  84. GetChildren.Request GetChildren.Response GetChildren ItemNetworkDataSource ItemRetrofitDataSource ItemDetailsViewModel

  85. GetChildren.Request GetChildren.Response GetChildren ItemNetworkDataSource ItemRetrofitDataSource ItemDetailsViewModel

  86. class GetChildren @Inject constructor(
 private val network: ItemNetworkDataSource) : UseCase<Request,

    Response> {
 
 override fun execute(request: Request): Observable<Response> {
 return network.getChildren(request.item)
 .map { Response(it) }
 }
 
 class Request(val item: Item) : UseCase.Request
 
 class Response(val items: List<Item>) : UseCase.Response
 }
  87. class GetChildren @Inject constructor(
 private val network: ItemNetworkDataSource) : UseCase<Request,

    Response> {
 
 override fun execute(request: Request): Observable<Response> {
 return network.getChildren(request.item)
 .map { Response(it) }
 }
 
 class Request(val item: Item) : UseCase.Request
 
 class Response(val items: List<Item>) : UseCase.Response
 }
  88. class GetChildren @Inject constructor(
 private val network: ItemNetworkDataSource) : UseCase<Request,

    Response> {
 
 override fun execute(request: Request): Observable<Response> {
 return network.getChildren(request.item)
 .map { Response(it) }
 }
 
 class Request(val item: Item) : UseCase.Request
 
 class Response(val items: List<Item>) : UseCase.Response
 }
  89. class GetChildren @Inject constructor(
 private val network: ItemNetworkDataSource) : UseCase<Request,

    Response> {
 
 override fun execute(request: Request): Observable<Response> {
 return network.getChildren(request.item)
 .map { Response(it) }
 }
 
 class Request(val item: Item) : UseCase.Request
 
 class Response(val items: List<Item>) : UseCase.Response
 }
  90. class GetChildren @Inject constructor(
 private val network: ItemNetworkDataSource) : UseCase<Request,

    Response> {
 
 override fun execute(request: Request): Observable<Response> {
 return network.getChildren(request.item)
 .map { Response(it) }
 }
 
 class Request(val item: Item) : UseCase.Request
 
 class Response(val items: List<Item>) : UseCase.Response
 }
  91. class GetChildren @Inject constructor(
 private val network: ItemNetworkDataSource) : UseCase<Request,

    Response> {
 
 override fun execute(request: Request): Observable<Response> {
 return network.getChildren(request.item)
 .map { Response(it) }
 }
 
 class Request(val item: Item) : UseCase.Request
 
 class Response(val items: List<Item>) : UseCase.Response
 }
  92. class GetChildren @Inject constructor(
 private val network: ItemNetworkDataSource) : UseCase<Request,

    Response> {
 
 override fun execute(request: Request): Observable<Response> {
 return network.getChildren(request.item)
 .map { Response(it) }
 }
 
 class Request(val item: Item) : UseCase.Request
 
 class Response(val items: List<Item>) : UseCase.Response
 }
  93. app/src/main/kotlin/clean/news/app/
 └── data
 ├── DataSource.kt
 ├── item
 │ ├── ItemDataSource.kt


    │ ├── ItemDiskDataSource.kt
 │ ├── ItemMemoryDataSource.kt
 │ └── ItemNetworkDataSource.kt
 └── user
 ├── UserDataSource.kt
 ├── UserDiskDataSource.kt
 ├── UserMemoryDataSource.kt
 └── UserNetworkDataSource.kt
  94. interface DataSource<T, ID> {
 fun getAll(): Observable<List<T>>
 
 fun getById(id:

    ID): Observable<T>
 
 fun save(t: T): Observable<Boolean>
 }
  95. interface ItemDataSource : DataSource<Item, Long> {
 fun getItems(listType: ListType): Observable<List<Item>>


    
 fun getChildren(item: Item): Observable<List<Item>>
 }
  96. interface ItemDiskDataSource : ItemDataSource interface ItemMemoryDataSource : ItemDataSource interface ItemNetworkDataSource

    : ItemDataSource
  97. Use Cases • Contain application specific business rules • Orchestrate

    the flow of data to and from entities • Unaffected by external systems like database or UI
  98. Interface Adapters

  99. “a design pattern that allows the interface of an existing

    class to be used as another interface.”
  100. apply plugin: 'java'
 
 dependencies {
 compile project(':app')
 } Core

    App Presentation Data
  101. ItemNetworkDataSource GetChildren ItemRetrofitDataSource ItemDetailViewModel

  102. ItemNetworkDataSource GetChildren ItemRetrofitDataSource ItemDetailViewModel

  103. ItemNetworkDataSource GetChildren ItemRetrofitDataSource ItemDetailViewModel

  104. ItemNetworkDataSource GetChildren ItemRetrofitDataSource ItemDetailViewModel

  105. ItemNetworkDataSource GetChildren ItemRetrofitDataSource ItemDetailViewModel

  106. presentation/src/main/kotlin/clean/news/presentation/model/
 ├── Model.kt
 ├── item
 │ ├── ItemDetailViewModel.kt
 │ ├──

    ItemListViewModel.kt
 │ └── ItemUrlViewModel.kt
 └── main
 └── MainViewModel.kt
  107. class ItemDetailViewModel @Inject constructor(
 private val getChildren: GetChildren,
 private val

    item: Item) : Model<Sources, Sinks> {
 
 private val children = getChildren.execute(Request(item))
 .map { it.items }
 .replay(1)
 .autoConnect()
 }
  108. class ItemDetailViewModel @Inject constructor(
 private val getChildren: GetChildren,
 private val

    item: Item) : Model<Sources, Sinks> {
 
 private val children = getChildren.execute(Request(item))
 .map { it.items }
 .replay(1)
 .autoConnect()
 }
  109. class ItemDetailViewModel @Inject constructor(
 private val getChildren: GetChildren,
 private val

    item: Item) : Model<Sources, Sinks> {
 
 private val children = getChildren.execute(Request(item))
 .map { it.items }
 .replay(1)
 .autoConnect()
 }
  110. data/src/main/kotlin/clean/news/data/
 ├── lru
 │ ├── ItemLruDataSource.kt
 │ └── UserLruDataSource.kt
 ├──

    retrofit
 │ ├── ItemRetrofitDataSource.kt
 │ └── UserRetrofitDataSource.kt
 └── sqlite
 ├── ItemSqliteDataSource.kt
 └── UserSqliteDataSource.kt
  111. class ItemRetrofitDataSource @Inject constructor(
 private val itemService: ItemService) : ItemNetworkDataSource

    {
 
 override fun getById(id: Long): Observable<Item> {
 return itemService.getById(id)
 }
 }
  112. class ItemRetrofitDataSource @Inject constructor(
 private val itemService: ItemService) : ItemNetworkDataSource

    {
 
 override fun getById(id: Long): Observable<Item> {
 return itemService.getById(id)
 }
 }
  113. class ItemRetrofitDataSource @Inject constructor(
 private val itemService: ItemService) : ItemNetworkDataSource

    {
 
 override fun getById(id: Long): Observable<Item> {
 return itemService.getById(id)
 }
 }
  114. interface ItemService { 
 @GET("item/{item_id}.json")
 fun getById(@Path("item_id") id: Long): Observable<Item>

    
 }
  115. class ItemRetrofitDataSource @Inject constructor(
 private val itemService: ItemService) : ItemNetworkDataSource

    {
 
 override fun getById(id: Long): Observable<Item> {
 return itemService.getById(id)
 }
 }
  116. Interface Adapters • Convert data between uses cases and external

    agencies • MVC, MVP, MVVM, RPM implementation • Database adapter • Network adapter
  117. Frameworks and Drivers

  118. “a universal, reusable software environment that provides particular functionality as

    part of a larger software platform to facilitate development”
  119. apply plugin: 'com.android.application'
 
 dependencies {
 compile project(':presentation')
 compile project(':data')


    } Core App Presentation Data Android Desktop
  120. class ItemDetailView : RelativeLayout { 
 @Inject
 lateinit var model:

    ItemDetailViewModel
 
 @JvmOverloads
 constructor(...) : super(context, attrs, defStyle) {
 ComponentService.getService<ItemDetailComponent>(context)?.inject(this)
 } }
  121. class ItemDetailView : RelativeLayout {
 @Inject
 lateinit var model: ItemDetailViewModel


    
 @JvmOverloads
 constructor(...) : super(context, attrs, defStyle) {
 ComponentService.getService<ItemDetailComponent>(context)?.inject(this)
 } }
  122. class ItemDetailView : RelativeLayout { 
 @Inject
 lateinit var model:

    ItemDetailViewModel
 
 @JvmOverloads
 constructor(...) : super(context, attrs, defStyle) {
 ComponentService.getService<ItemDetailComponent>(context)?.inject(this)
 } }
  123. @Module
 class DataModule { 
 @Provides
 @ApplicationScope
 fun itemService(retrofit: Retrofit):

    ItemService {
 return retrofit.create(ItemService::class.java)
 }
 
 @Provides
 @ApplicationScope
 fun itemNetworkDataSource(itemService: ItemService): ItemNetworkDataSource {
 return ItemRetrofitDataSource(itemService)
 }
 
 }
  124. @Module
 class DataModule { 
 @Provides
 @ApplicationScope
 fun itemService(retrofit: Retrofit):

    ItemService {
 return retrofit.create(ItemService::class.java)
 }
 
 @Provides
 @ApplicationScope
 fun itemNetworkDataSource(itemService: ItemService): ItemNetworkDataSource {
 return ItemRetrofitDataSource(itemService)
 }
 
 }
  125. @Module
 class DataModule { 
 @Provides
 @ApplicationScope
 fun itemService(retrofit: Retrofit):

    ItemService {
 return retrofit.create(ItemService::class.java)
 }
 
 @Provides
 @ApplicationScope
 fun itemNetworkDataSource(itemService: ItemService): ItemNetworkDataSource {
 return ItemRetrofitDataSource(itemService)
 }
 
 }
  126. override fun onAttachedToWindow() {
 super.onAttachedToWindow()
 val sinks = model.setUp(Sources(
 toolbar.navigationClicks(),


    Observable.empty(),
 toolbar.itemClicks()
 .filter {it.itemId == R.id.item_share }
 .map { Unit }
 ))
 }
  127. override fun onAttachedToWindow() {
 super.onAttachedToWindow()
 val sinks = model.setUp(Sources(
 toolbar.navigationClicks(),


    Observable.empty(),
 toolbar.itemClicks()
 .filter {it.itemId == R.id.item_share }
 .map { Unit }
 ))
 }
  128. Frameworks and Drivers • Glue code to communicate inwards •

    Delivery mechanism • Dependency providers
  129. POLICIES MECHANISMS

  130. Resources • https://github.com/pardom/CleanNews • https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html • https://cleancoders.com/episode/clean-code-episode-7/show

  131. Questions? @pardom michaelpardo.com