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

API Design as an Art

85a1166e93654865b4dcafdafe2b2dfd?s=47 Zac Sweers
November 05, 2017

API Design as an Art

As developers, we are constantly interacting with APIs. Good ones can make feature iterations a breeze, and bad ones can waste hours of developer productivity. But we rarely stop to consider how they fit into our own work or how to be cognizant of potential design pitfalls. Using AutoDispose, an open source reactive tool for automatic stream disposal, as a case study, this talk will evaluate strategies and best practices for iteratively fine tuning real world APIs for better testing and the overall developer experience of your consumers.

85a1166e93654865b4dcafdafe2b2dfd?s=128

Zac Sweers

November 05, 2017
Tweet

More Decks by Zac Sweers

Other Decks in Programming

Transcript

  1. API Design as an Art Zac Sweers - Uber @pandanomic

  2. aWhat makes ana API?

  3. aWhat makes ana API bad?

  4. None
  5. Developer experience API Design

  6. None
  7. Developer experience API Design

  8. None
  9. None
  10. None
  11. aWhat makes ana API bad?

  12. What makes an API good? !

  13. What makes an API good?

  14. None
  15. Your lib Picasso Dagger ROOM Support library Internal libs Butter

    Knife RxJava Kotlin ⛏
  16. AutoDispose

  17. AutoDispose • RxJava 2 utility for automatic stream disposal

  18. AutoDispose • RxJava 2 utility for automatic stream disposal •

    Originally built in a side project ~10/2016
  19. AutoDispose • RxJava 2 utility for automatic stream disposal •

    Originally built in a side project ~10/2016 • Mainlined to Uber ~12/2016
  20. AutoDispose • RxJava 2 utility for automatic stream disposal •

    Originally built in a side project ~10/2016 • Mainlined to Uber ~12/2016 • Open sourced 3/2017
  21. Observable.just(1) .subscribe()

  22. apiRequest() .subscribe()

  23. apiRequest() // 200+ ms .subscribe()

  24. apiRequest() // 200+ ms .subscribeOn(io()) .observeOn(mainThread()) .subscribe()

  25. apiRequest() // 200+ ms .subscribeOn(io()) .observeOn(mainThread()) .subscribe() // DetailActivity.kt Memory

    leak
  26. val disposable = apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .subscribe()

  27. val disposable = apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .subscribe() // Later disposable.dispose()

  28. val disposable = apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .subscribe()

  29. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .subscribe()

  30. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(AutoDispose.with(this).forObservable()) .subscribe()

  31. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(AutoDispose .with(this) // Scope .forObservable()) // Type

    .subscribe()
  32. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(AutoDispose .with(this) // Scope .forObservable()) // Type

    .subscribe()
  33. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(AutoDispose .with(this) // Scope .forObservable()) // Type

    .subscribe()
  34. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(AutoDispose .with(this) // Scope .forObservable()) // Type

    .subscribe()
  35. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(AutoDispose .with(this) // Scope .forObservable()) // Type

    .subscribe()
  36. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(AutoDispose .with(this) .forObservable()) .subscribe()

  37. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(AutoDispose .with(Maybe.never()) .forObservable()) .subscribe()

  38. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(AutoDispose .with(this) .forObservable()) .subscribe()

  39. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(AutoDispose .with(this) // ScopeProvider .forObservable()) .subscribe()

  40. interface ScopeProvider { fun requestScope(): Maybe<*> }

  41. interface LifecycleScopeProvider<E> { fun correspondingEvents(): Function<E, E> fun peekLifecycle(): E

    fun lifecycle(): Observable<E> }
  42. interface ScopeProvider { fun requestScope(): Maybe<*> }

  43. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(AutoDispose .with(this) // ScopeProvider .forObservable()) .subscribe()

  44. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .subscribe()

  45. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .subscribe() .disposeOn(lifecycle)

  46. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .subscribe { // Stuff } .disposeOn(lifecycle)

  47. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .subscribe { // Stuff // Stuff //

    Stuff // Stuff // Stuff // Stuff }
  48. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .subscribe { // Stuff // Stuff //

    Stuff // Stuff // Stuff // Stuff } .disposeOn(lifecycle)
  49. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .subscribe({ r -> // Handle response })

    { error: Throwable -> // Stuff // Stuff } .disposeOn(lifecycle)
  50. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .delaySubscription(attaches()) .subscribe() .disposeOn(lifecycle)

  51. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .delaySubscription(attaches()) .subscribe(myObserver) .disposeOn(lifecycle)

  52. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .delaySubscription(attaches()) .subscribe(myObserver) .disposeOn(lifecycle)

  53. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .subscribe()

  54. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .subscribe(BoundObservers.<Response>forObservable(lifecycle) .onNext(...) .create())

  55. .subscribe(BoundObservers.<Response>forObservable(lifecycle) .onNext(...) .create())

  56. .subscribe(BoundObservers.<Response>forObservable(lifecycle) .onNext(...) .create())

  57. .subscribe(BoundObservers.<Response>forObservable(lifecycle) .onNext(...) .create())

  58. .subscribe(BoundObservers.<Response>forObservable(lifecycle) .onNext(...) .create())

  59. .subscribe(BoundObservers.<Response>forObservable(lifecycle) .onNext(...) .onError(...) .create())

  60. .subscribe(BoundObservers.<Response>forObservable(lifecycle) .onNext(...) .create())

  61. .subscribe(BoundObservers.<Response>forObservable(lifecycle) .onNext(...) .create())

  62. .subscribe(BoundObservers.<Response>forObservable(lifecycle) .onNext(...) .onError(...) .create())

  63. .subscribe(BoundObservers.<Response>forObservable(lifecycle) .onNext(...) .onError(...) .onComplete(...) .create())

  64. .subscribe()

  65. .subscribe(BoundObservers.<Response>forObservable(lifecycle) .create())

  66. .subscribe(BoundObservers.<Response>forObservable(lifecycle) .create())

  67. .subscribe(BoundObservers.forObservable(lifecycle) .create())

  68. .subscribe(BoundObservers.forObservable(lifecycle) .onNext(Object o -> ???) // ಠ_ಠ .create())

  69. .subscribe(BoundObservers.<Response>forObservable(lifecycle) .onNext(Object o -> ???) // ಠ_ಠ .create())

  70. .subscribe(BoundObservers.<Response>forObservable(lifecycle) .onNext(Response r -> ...) // :D .create())

  71. .subscribe(BoundObservers.<Response>forObservable(lifecycle) .create())

  72. ResponseObserver o = ResponseObserver() .subscribe(BoundObservers.<Response>forObservable(lifecycle) .create())

  73. class ResponseObserver : BoundObserver<Response>() { // :( }

  74. ResponseObserver o = ResponseObserver() .subscribe(BoundObservers.<Response>forObservable(lifecycle) .create())

  75. ResponseObserver o = ResponseObserver() PublishSubject<Response> sub = PublishSubject.create() .subscribe(BoundObservers.<Response>forObservable(lifecycle) .create())

  76. PublishSubject<Response> sub = PublishSubject.create() .subscribe(BoundObservers.<Response>forObservable(lifecycle) .create())

  77. PublishSubject<Response> sub = PublishSubject.create() .subscribe(BoundObservers.<Response>forObservable(lifecycle) .around(sub))

  78. PublishSubject<Response> sub = PublishSubject.create() .subscribe(BoundObservers.<Response>forObservable(lifecycle) .around(sub))

  79. PublishSubject<Response> sub = PublishSubject.create() .subscribe(BoundObservers.<Response>forObservable(lifecycle) .onNext(...) .around(sub))

  80. PublishSubject<Response> sub = PublishSubject.create() .subscribe(BoundObservers.<Response>forObservable(lifecycle) .onNext(...) .around(sub))

  81. PublishSubject<Response> sub = PublishSubject.create() .subscribe(BoundObservers.<Response>forObservable(lifecycle) .onNext(...) .onNext(...) .onNext(...) .onNext(...) .onNext(...)

    .onNext(...) .onNext(...) .around(sub))
  82. .subscribe(BoundObservers.<Response>forObservable(lifecycle) .create())

  83. .subscribe(AutoDispose.<Response>forObservable(lifecycle) .create())

  84. .subscribe(AutoDispose.<Response>forObservable(lifecycle) .around(...))

  85. fun around(o: Observer<T>) fun around(c: Consumer<T>) fun around(c: Consumer<T>, e:

    Consumer<Throwable>) fun around(c: Consumer<T>, e: Consumer<Throwable>, a: Action) // Etc
  86. fun around(o: Observer<T>) fun around(c: Consumer<T>) fun around(c: Consumer<T>, e:

    Consumer<Throwable>) fun around(c: Consumer<T>, e: Consumer<Throwable>, a: Action) fun empty() // Etc
  87. interface AroundClause<T> { fun around(o: Observer<T>) fun around(c: Consumer<T>) fun

    around(c: Consumer<T>, e: Consumer<Throwable>) fun around(c: Consumer<T>, e: Consumer<Throwable>, a: Action) fun empty() // Etc }
  88. ResponseObserver o = new ResponseObserver() .subscribe(AutoDispose.<Response>forObservable(lifecycle) .around(o)) V2 : AroundClause

  89. ResponseObserver o = new ResponseObserver() .subscribe(AutoDispose.<Response>forObservable(lifecycle) .around(o))

  90. ResponseObserver o = new ResponseObserver() .subscribe(AutoDispose.<Response>observable() .scopeWith(lifecycle) .around(o)) V3

  91. ResponseObserver o = new ResponseObserver() .subscribe(AutoDispose.<Response>observable() .scopeWith(scope) .around(o)) V3

  92. ResponseObserver o = new ResponseObserver() .subscribe(AutoDispose.<Response>observable() .scopeWith(scope) .around(o))

  93. ResponseObserver o = new ResponseObserver() .subscribe(AutoDispose.<Response>observable() .scopeWith(scope) .around(o))

  94. ResponseObserver o = new ResponseObserver() .subscribe(AutoDispose.<Response>observable() .scopeWith(scope) .around(o))

  95. ResponseObserver o = new ResponseObserver() .subscribe(AutoDispose.<Response>observable() .scopeWith(scope) .around(o))

  96. interface AroundClause<T> { fun around(o: Observer<T>) fun around(c: Consumer<T>) fun

    around(c: Consumer<T>, e: Consumer<Throwable>) fun around(c: Consumer<T>, e: Consumer<Throwable>, a: Action) fun empty() // Etc }
  97. interface AroundClause { fun <T> around(o: Observer<T>) fun <T> around(c:

    Consumer<T>) fun <T> around(c: Consumer<T>, e: Consumer<Throwable>) fun <T> around(c: Consumer<T>, e: Consumer<Throwable>, a: Action) fun empty() // Etc }
  98. ResponseObserver o = new ResponseObserver() .subscribe(AutoDispose.<Response>observable() .scopeWith(scope) .around(o))

  99. ResponseObserver o = new ResponseObserver() .subscribe(AutoDispose.observable() .scopeWith(scope) .around(o))

  100. ResponseObserver o = new ResponseObserver() .subscribe(AutoDispose.observable() .scopeWith(scope) .around(new Consumer<Response>() {

    // Impl }))
  101. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .subscribe(AutoDispose.observable() .scopeWith(scope) .around(new Consumer<Response>() { // Impl

    })) V4
  102. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .subscribe(AutoDispose.observable() .scopeWith(scope) .around(new Consumer<Response>() { // Impl

    }))
  103. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .subscribe(AutoDispose.observable() .scopeWith(scope) .around(new Consumer<Response>() { @Override public

    void accept(Response r) { handleResponse(r) } }))
  104. .to()

  105. .to(new Function<Observable<T>, ???>() {})

  106. .to(new Function<Observable<T>, AroundClause>() {})

  107. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .subscribe(AutoDispose.observable() .scopeWith(scope) .around(new Consumer<Response>() { @Override public

    void accept(Response r) { handleResponse(r) }x }))
  108. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(???) .subscribe(AutoDispose.observable() .scopeWith(scope) .around(new Consumer<Response>() { @Override

    public void accept(Response r) { handleResponse(r) }x }))
  109. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(???) .around(new Consumer<Response>() { @Override public void

    accept(Response r) { handleResponse(r) }x }))
  110. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(AutoDispose.observable() .scopeWith(scope)) .around(new Consumer<Response>() { @Override public

    void accept(Response r) { handleResponse(r) }x }))
  111. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(AutoDispose.observable() .scopeWith(scope)) .around(new Consumer<Object>() { @Override public

    void accept(Object r) { handleResponse(r) }x }))
  112. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(AutoDispose.observable() .scopeWith(scope)) .around(new Consumer<Object>() { @Override public

    void accept(Object r) { // (╯°□°)╯︵ ┻━┻ }x }))
  113. class ObservableScoper<T> implements Function<Observable<T>, AroundClause> { }v

  114. class ObservableScoper<T> implements Function<Observable<T>, AroundClause> { ObservableScoper(Maybe<?> scope) { //

    ... }a }v
  115. class ObservableScoper<T> implements Function<Observable<T>, AroundClause> { ObservableScoper(Maybe<?> scope) { //

    ... }a @Override public AroundClause apply(Observable<T> o) { }b }v
  116. class ObservableScoper<T> implements Function<Observable<T>, AroundClause> { ObservableScoper(Maybe<?> scope) { //

    ... }a @Override public AroundClause apply(Observable<T> o) { return new AroundClause() { // ... }c }b }v
  117. class ObservableScoper<T> implements Function<Observable<T>, AroundClause<T>> { ObservableScoper(Maybe<?> scope) { //

    ... }a @Override public AroundClause<T> apply(Observable<T> o) { return new AroundClause<T>() { // ... }c }b }v
  118. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(new ObservableScoper<Response>(scope)) .around(new Consumer<Response>() { @Override public

    void accept(Response r) { handleResponse(r) } }))
  119. interface AroundClause<T> { fun around(o: Observer<T>) fun around(c: Consumer<T>) fun

    around(c: Consumer<T>, e: Consumer<Throwable>) fun around(c: Consumer<T>, e: Consumer<Throwable>, a: Action) fun empty() // Etc }
  120. interface SubscribeProxy<T> { fun around(o: Observer<T>) fun around(c: Consumer<T>) fun

    around(c: Consumer<T>, e: Consumer<Throwable>) fun around(c: Consumer<T>, e: Consumer<Throwable>, a: Action) fun empty() // Etc }
  121. interface SubscribeProxy<T> { fun subscribe(o: Observer<T>) fun subscribe(c: Consumer<T>) fun

    subscribe(c: Consumer<T>, e: Consumer<Throwable>) fun subscribe(c: Consumer<T>, e: Consumer<Throwable>, a: Action) fun empty() // Etc }
  122. interface SubscribeProxy<T> { fun subscribe(o: Observer<T>) fun subscribe(c: Consumer<T>) fun

    subscribe(c: Consumer<T>, e: Consumer<Throwable>) fun subscribe(c: Consumer<T>, e: Consumer<Throwable>, a: Action) fun subscribe() // Etc }
  123. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(new ObservableScoper<Response>(scope)) .around(new Consumer<Response>() { @Override public

    void accept(Response r) { handleResponse(r) } }))
  124. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(new ObservableScoper<Response>(scope)) .subscribe(new Consumer<Response>() { @Override public

    void accept(Response r) { handleResponse(r) } }))
  125. –Nick Butcher, and probably other people “If it looks right,

    it is right.”
  126. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(new ObservableScoper<Response>(scope)) .subscribe(newxConsumer<Response>() { @Override public void

    accept(Response r) { handleResponse(r) }x })) V5
  127. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(AutoDispose.with(scope).<Response>forObservable()) .subscribe(newxConsumer<Response>() { @Override public void accept(Response

    r) { handleResponse(r) }x }))
  128. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(AutoDispose.with(scope).forObservable()) .subscribe(new Consumer<Response>() { @Override public void

    accept(Response r) { handleResponse(r) }x }))
  129. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(AutoDispose.forObservable(scope)) .subscribe(new Consumer<Response>() { @Override public void

    accept(Response r) { handleResponse(r) }x }))
  130. None
  131. https://youtrack.jetbrains.com/issue/ IDEA-179864

  132. apiRequest() .subscribeOn(io()) .observeOn(mainThread()) .to(AutoDispose.forObservable(scope)) .subscribe(new Consumer<Response>() { @Override public void

    accept(Response r) { handleResponse(r) }x }))
  133. aWhat makes ana API good? !

  134. “Prefer exposing interfaces”

  135. “Prefer exposing interfaces” interface SubscribeProxy<T> { fun subscribe(o: Observer<T>) fun

    subscribe(c: Consumer<T>) fun subscribe(c: Consumer<T>, e: Consumer<Throwable>) fun subscribe(c: Consumer<T>, e: Consumer<Throwable>, a: Action) fun subscribe() // Etc }
  136. “Prefer composition in accepting external implementations. Inheritance can be inconvenient.”

  137. “Prefer composition in accepting external implementations. Inheritance can be inconvenient.”

    class ResponseObserver : BoundObserver<Response>() { // :( } PublishSubject<Response> sub = PublishSubject.create()
  138. “Prefer final by default”

  139. “Identify footguns”

  140. “Identify footguns” .subscribe(BoundObservers.<Response>forObservable(lifecycle) .onNext(...) .onNext(...) .onNext(...) .onNext(...) .onNext(...) .onNext(...) .onNext(...)

    .around(sub))
  141. “Dependency on lint should be avoided”

  142. “Dependency on lint should be avoided”

  143. “IDE experience matters”

  144. “IDE experience matters” .around(new Consumer<Object>() { @Override public void accept(Object

    r) { // (╯°□°)╯︵ ┻━┻ }x }))
  145. “Crack eggs, make omelettes” V4 V5 V0 V3 V1

  146. None
  147. Questions? Zac Sweers - Uber @pandanomic