Slide 1

Slide 1 text

Reactive Clean Architecture Viktor Nyblom @qw4z1

Slide 2

Slide 2 text

MVP MVC MVVM What isn’t Architecture X X X

Slide 3

Slide 3 text

Why clean architecture?

Slide 4

Slide 4 text

Architecture is about Intent - Uncle Bob

Slide 5

Slide 5 text

Showing intent

Slide 6

Slide 6 text

Showing intent

Slide 7

Slide 7 text

Key points ! Use Cases ! Dependencies ! Models ! Reactive ! Testable ! Independent of frameworks

Slide 8

Slide 8 text

What is a Use Case? ! Captures application business rules ! Independent of framework ! Shows WHAT the application does, not HOW X Interactor

Slide 9

Slide 9 text

Layered architecture Interactors Repositories UI DB HTTP Presenters Dependencies

Slide 10

Slide 10 text

Layered architecture Use Cases Repositories UI DB HTTP Presenters Data Domain Presentation

Slide 11

Slide 11 text

Three Layers Data Presentation Domain Model Presenter View Interactor Model Operation API Repository Cache DB Abstraction

Slide 12

Slide 12 text

Presentation data flow View Presenter D O M A I N User interaction onRefresh() execute() domainData format() showData(data) Get
 Feed
 Interactor fun getFeed(): Single>

Slide 13

Slide 13 text

What if we could observe a stream of data forever and react to new versions of that data?

Slide 14

Slide 14 text

Presentation data flow View Presenter D O M A I N User interaction onRefresh() execute() domainData format() updateData(data) Refresh
 Feed
 Interactor Observe
 Feed
 Interactor Create
 Tweet
 Interactor execute() onCreateTweet()

Slide 15

Slide 15 text

interface TweetFeedContract.Presenter
 : BasePresenter { fun onRefresh() ... } Presentation

Slide 16

Slide 16 text

interface TweetFeedContract.View
 : BaseView { fun updateFeed(list: List) fun showEmpty() fun showLoading() .... } Presentation

Slide 17

Slide 17 text

class TweetFeedPresenter @Inject constructor(
 private val refreshFeedInteractor: RefreshFeedInteractor,
 private val observeFeedInteractor: ObserveFeedInteractor,
 private val mapToDisplayableItem: Mapper
 ) : TweetFeedContract.Presenter { private val disposables: CompositeDisposable
 ... } Presentation

Slide 18

Slide 18 text

override fun onRefresh() {
 refreshFeedInteractor.excecute()
 } TweetFeedPresenter

Slide 19

Slide 19 text

override fun onAttach(view: TweetFeedContract.View) {
 this.view = view
 disposables += bindToFeed()
 } TweetFeedPresenter

Slide 20

Slide 20 text

override fun bindToFeed(): Disposable =
 observeFeedInteractor
 .excecute()
 .onSubscribe { view?.showLoading() }
 .map(mapToDisplayableItem)
 .subscribe( 
 { tweets -> 
 if (tweets.notEmpty())
 view?.updateFeed(tweets)
 else
 view?.showEmpty()
 },
 { throwable -> loge(throwable) } )
 TweetFeedPresenter

Slide 21

Slide 21 text

Models should make sense in the context where they are being used.

Slide 22

Slide 22 text

What the map? public class Item { private String validTo;
 private float price; private String validFrom; private int type; }

Slide 23

Slide 23 text

What the map? public class Item { @SerializedName ("validto") private String validTo; @SerializedName ("price")
 private float price; @SerializedName ("validfrom") private String validFrom; @SerializedName (value = "type") private int type; }

Slide 24

Slide 24 text

What the map? @DatabaseTable (name = “item")
 public class Item { @DatabaseField (generatedId = true) private int _id; @DatabaseField @SerializedName ("validto") private String validTo; @DatabaseField (canBeNull = false) @SerializedName ("price")
 private float price; @DatabaseField (canBeNull = false) @SerializedName ("validfrom") private String validFrom; @DatabaseField @SerializedName (value = "type") private int type; }

Slide 25

Slide 25 text

What the map? public class APIItem { private int validto;
 private float price; private int validfrom; private int type; } @DatabaseTable (name = “item")
 public class DatabaseItem { @DatabaseField (generatedId = true) private int _id;
 @DatabaseField private int validTo;
 @DatabaseField
 private float price; @DatabaseField
 private int validFrom; @DatabaseField
 private int type; @DatabaseField
 private boolean dirty; }

Slide 26

Slide 26 text

What the map? @DatabaseTable (name = “item")
 public class DatabaseItem { @DatabaseField (generatedId = true) private int _id;
 @DatabaseField private int validTo;
 @DatabaseField
 private float price; @DatabaseField
 private int validFrom; @DatabaseField
 private int type; @DatabaseField
 private boolean dirty; } 
 public class DisplayableItem { private int _id; private DateTime validTo; private String formattedTo;
 private String formattedPrice;
 private DateTime validFrom;
 private String formattedFrom;
 private ItemType type;
 private boolean selected; }

Slide 27

Slide 27 text

What the map Data Presentation Domain Model Presenter View Interactor Model Operation API Repository Cache DB Abstraction

Slide 28

Slide 28 text

What the map Data Presentation Domain Presentation
 Model Presenter View Interactor Domain
 Model Operation API Repository Cache DB Abstraction API
 Model Cache
 Model

Slide 29

Slide 29 text

Domain data flow D A T A execute() domainData domainData Refresh
 Feed
 Interactor Abstraction
 : Tweet Repository P R E S E N TA T
 I
 O N Observe
 Feed
 Interactor onComplete() retrieveFeed() Abstraction
 : TweetList
 Repository onNext()

Slide 30

Slide 30 text

Interactors abstract class CompletableInteractor( private val executionScheduler: Scheduler, private val postExecutionScheduler: Scheduler) { abstract fun buildInteractorCompletable(params: Params): Completable fun execute(params: Params): Completable = buildInteractorObservable(params) .subscribeOn(executionScheduler) .observeOn(postExecutionScheduler) .subscribe() }

Slide 31

Slide 31 text

Interactors class RefreshFeedInteractor @Inject constructor( private val tweetRepository: TweetRepository, executionScheduler: Scheduler, postExecutionScheduler: Scheduler) : CompletableInteractor(executionScheduler, postExecutionScheduler) { override fun buildInteractorCompletable(params: FeedSpec): Completable = tweetRepository.refreshFeed(params) }

Slide 32

Slide 32 text

Interactors abstract class ObservableInteractor( private val executionScheduler: Scheduler, private val postExecutionScheduler: Scheduler) { abstract fun buildInteractorCompletable(params: Params): Observable fun execute(params: Params): Observable = buildInteractorObservable(params) .subscribeOn(executionScheduler) .observeOn(postExecutionScheduler) .subscribe() }

Slide 33

Slide 33 text

Interactors class RefreshFeedInteractor @Inject constructor( private val tweetListRepository: TweetListRepository, executionScheduler: Scheduler, postExecutionScheduler: Scheduler) : ObservableInteractor, FeedSpec>(executionScheduler, postExecutionScheduler) { override fun buildInteractorCompletable(params: FeedSpec): Observable> = tweetListRepository.observeFeed(params) } .filter { it.createdDate.after(someDate) } .compose { customTransformer }

Slide 34

Slide 34 text

interface TweetListRepository { fun refreshFeed(feedSpec: FeedSpec): Completable fun observeFeed(feedSpec: FeedSpec): Observable> }

Slide 35

Slide 35 text

class ReactiveTwitterRepository( private val apiMapper: ApiToStoreMapper, 
 private val api: SomeApi, 
 private val store: ReactiveStore) : TweetRepository { override fun refreshFeed(): Completable =
 api.retrieveTweetList()
 .map { apiMapper }
 .doOnSuccess { store.storeTweets(it) }
 .toCompletable() ... }

Slide 36

Slide 36 text

class ReactiveTwitterRepository( private val apiMapper: ApiToStoreMapper, 
 private val domainMapper: StoreToDomainMapper, 
 private val api: SomeApi, 
 private val store: ReactiveStore) : TweetRepository { ... override fun getFeedObservable(feedSpec: FeedSpec):
 Observable =
 store.getFeedObservable()
 .map(domainMapper) ... }

Slide 37

Slide 37 text

interface ReactiveStore { fun storeSingle(item: Value): Completable fun storeAll(items: List): Completable fun replaceAll(items: List): Completable fun getSingle(key: Key): Observable fun getAll(): Observable> } ReactiveStore

Slide 38

Slide 38 text

ReactiveStore ! Coordinates putting data in persistence and/or cache ! Create, store and feed the stream ! Implementation depends on the application ! Single source of truth

Slide 39

Slide 39 text

Domain data flow D A T A action P R E S E N TA T
 I
 O N D O M A I N action reaction reaction

Slide 40

Slide 40 text

Final advices ! Be pragmatic ○ Some compromises are ok ○ Perfect isn’t always best ! Beware of technical debt ○ Every debt will be collected some day ! Start out small ○ Get a feel for the structure ○ Decide on whether it’s the best approach for your app ! Don’t forget the tests!

Slide 41

Slide 41 text

Q&A (hopefully) [email protected] Viktor Nyblom @qw4z1