Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Clean Architecture

Clean Architecture

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

Michael Pardo

April 30, 2016
Tweet

More Decks by Michael Pardo

Other Decks in Programming

Transcript

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. Entities Use cases Data sources, Presentation models Delivery mechanisms, databases

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

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

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

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

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

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

    ├── GetItemsByListType
 │ └── SaveItem
 └── user
 ├── GetUserById
 └── SaveUser
  13. “An entity is something that exists as itself, as a

    subject or as an object, actually or potentially, concretely or abstractly, physically or not.”
  14. 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)
 }
 }
  15. 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)
 }
 }
  16. data class User(
 val id: String,
 val delay: Int,
 val

    created: Date,
 val karma: Int,
 val about: String,
 val submitted: List<Long>
 )
  17. “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.”
  18. 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.
  19. 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.
  20. 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
  21. interface UseCase<I : Request, O : Response> {
 
 fun

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

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

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

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

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

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

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

    execute(request: I): Observable<O>
 
 interface Request
 
 interface Response
 
 }
  29. interface UseCase3<A, B, C, R> {.
 
 fun execute(arg1: A,

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

    GetItemById.kt
 │ ├── GetItemsByListType.kt
 │ └── SaveItem.kt
 └── user
 ├── GetUserById.kt
 └── SaveUser.kt
  31. 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
 }
  32. 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
 }
  33. 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
 }
  34. 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
 }
  35. 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
 }
  36. 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
 }
  37. 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
 }
  38. 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
  39. interface DataSource<T, ID> {
 fun getAll(): Observable<List<T>>
 
 fun getById(id:

    ID): Observable<T>
 
 fun save(t: T): Observable<Boolean>
 }
  40. Use Cases • Contain application specific business rules • Orchestrate

    the flow of data to and from entities • Unaffected by external systems like database or UI
  41. “a design pattern that allows the interface of an existing

    class to be used as another interface.”
  42. presentation/src/main/kotlin/clean/news/presentation/model/
 ├── Model.kt
 ├── item
 │ ├── ItemDetailViewModel.kt
 │ ├──

    ItemListViewModel.kt
 │ └── ItemUrlViewModel.kt
 └── main
 └── MainViewModel.kt
  43. 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()
 }
  44. 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()
 }
  45. 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()
 }
  46. data/src/main/kotlin/clean/news/data/
 ├── lru
 │ ├── ItemLruDataSource.kt
 │ └── UserLruDataSource.kt
 ├──

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

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

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

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

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

    agencies • MVC, MVP, MVVM, RPM implementation • Database adapter • Network adapter
  52. “a universal, reusable software environment that provides particular functionality as

    part of a larger software platform to facilitate development”
  53. class ItemDetailView : RelativeLayout { 
 @Inject
 lateinit var model:

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


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

    ItemDetailViewModel
 
 @JvmOverloads
 constructor(...) : super(context, attrs, defStyle) {
 ComponentService.getService<ItemDetailComponent>(context)?.inject(this)
 } }
  56. @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)
 }
 
 }
  57. @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)
 }
 
 }
  58. @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)
 }
 
 }
  59. 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 }
 ))
 }
  60. 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 }
 ))
 }
  61. Frameworks and Drivers • Glue code to communicate inwards •

    Delivery mechanism • Dependency providers