Reactive Clean Architecture
Viktor Nyblom
@qw4z1
MVP MVC MVVM
What isn’t Architecture
X
X
X
Why clean architecture?
Architecture is about Intent
- Uncle Bob
Showing intent
Showing intent
Key points
! Use Cases
! Dependencies
! Models
! Reactive
! Testable
! Independent of frameworks
What is a Use Case?
! Captures application business rules
! Independent of framework
! Shows WHAT the application does, not HOWX
Interactor
Layered architecture
Interactors
Repositories
UI
DB
HTTP
Presenters
Dependencies
Layered architecture
Use Cases
Repositories
UI
DB
HTTP
Presenters
Data
Domain
Presentation
Three Layers
DataPresentation Domain
Model
PresenterView Interactor
Model
Operation
API
Repository
Cache
DBAbstraction
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<List<Tweets>>
What if we could observe a stream of data forever
and react to new versions of that data?
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()
interface TweetFeedContract.Presenter

: BasePresenter<TweetFeedContract.View> {
fun onRefresh()
...
}
Presentation
interface TweetFeedContract.View

: BaseView {
fun updateFeed(list: List<DisplayableTweet>)
fun showEmpty()
fun showLoading()
....
}
Presentation
class TweetFeedPresenter @Inject constructor(

private val refreshFeedInteractor: RefreshFeedInteractor,

private val observeFeedInteractor: ObserveFeedInteractor,

private val mapToDisplayableItem: Mapper<DisplayableItem>

) : TweetFeedContract.Presenter {
private val disposables: CompositeDisposable

...
}
Presentation
override fun onRefresh() {

refreshFeedInteractor.excecute()

}
TweetFeedPresenter
override fun onAttach(view: TweetFeedContract.View) {

this.view = view

disposables += bindToFeed()

}
TweetFeedPresenter
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
Models should make sense in the context where
they are being used.
What the map?
public class Item {
private String validTo;

private float price;
private String validFrom;
private int type;
}
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;
}
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;
}
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;
}
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;
}
What the map
DataPresentation Domain
Model
PresenterView Interactor
Model
Operation
API
Repository
Cache
DBAbstraction
What the map
DataPresentation Domain
Presentation

Model
PresenterView Interactor
Domain

Model
Operation
API
Repository
Cache
DBAbstraction
API

Model
Cache

Model
Domain data flow
D
A
T
A
execute()
domainDatadomainData
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()
Interactors
abstract class CompletableInteractor<in Params>(
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()
}
Interactors
class RefreshFeedInteractor @Inject constructor(
private val tweetRepository: TweetRepository,
executionScheduler: Scheduler,
postExecutionScheduler: Scheduler)
: CompletableInteractor<FeedSpec>(executionScheduler, postExecutionScheduler) {
override fun buildInteractorCompletable(params: FeedSpec): Completable
= tweetRepository.refreshFeed(params)
}
Interactors
abstract class ObservableInteractor<T, in Params>(
private val executionScheduler: Scheduler,
private val postExecutionScheduler: Scheduler) {
abstract fun buildInteractorCompletable(params: Params): Observable<T>
fun execute(params: Params): Observable<T> =
buildInteractorObservable(params)
.subscribeOn(executionScheduler)
.observeOn(postExecutionScheduler)
.subscribe()
}
Interactors
class RefreshFeedInteractor @Inject constructor(
private val tweetListRepository: TweetListRepository,
executionScheduler: Scheduler,
postExecutionScheduler: Scheduler)
: ObservableInteractor<List<Tweet>, FeedSpec>(executionScheduler, postExecutionScheduler) {
override fun buildInteractorCompletable(params: FeedSpec): Observable<List<Tweet>>
= tweetListRepository.observeFeed(params)
}
.filter { it.createdDate.after(someDate) }
.compose { customTransformer }
interface TweetListRepository {
fun refreshFeed(feedSpec: FeedSpec): Completable
fun observeFeed(feedSpec: FeedSpec): Observable<List<Tweet>>
}
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()
...
}
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<List<Tweet> =

store.getFeedObservable()

.map(domainMapper)
...
}
interface ReactiveStore<Key, Value> {
fun storeSingle(item: Value): Completable
fun storeAll(items: List<Value>): Completable
fun replaceAll(items: List<Value>): Completable
fun getSingle(key: Key): Observable<Value>
fun getAll(): Observable<List<Value>>
}
ReactiveStore
ReactiveStore
! Coordinates putting data in persistence and/or cache
! Create, store and feed the stream
! Implementation depends on the application
! Single source of truth
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
reactionreaction
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!
Q&A
(hopefully)
viktor@devies.se
Viktor Nyblom
@qw4z1

Reactive clean architecture

  • 1.
  • 2.
    MVP MVC MVVM Whatisn’t Architecture X X X
  • 3.
  • 4.
    Architecture is aboutIntent - Uncle Bob
  • 5.
  • 6.
  • 7.
    Key points ! UseCases ! Dependencies ! Models ! Reactive ! Testable ! Independent of frameworks
  • 8.
    What is aUse Case? ! Captures application business rules ! Independent of framework ! Shows WHAT the application does, not HOWX Interactor
  • 9.
  • 10.
  • 11.
    Three Layers DataPresentation Domain Model PresenterViewInteractor Model Operation API Repository Cache DBAbstraction
  • 12.
    Presentation data flow ViewPresenter D O M A I N User interaction onRefresh() execute() domainData format() showData(data) Get
 Feed
 Interactor fun getFeed(): Single<List<Tweets>>
  • 13.
    What if wecould observe a stream of data forever and react to new versions of that data?
  • 14.
    Presentation data flow ViewPresenter 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()
  • 15.
  • 16.
    interface TweetFeedContract.View
 : BaseView{ fun updateFeed(list: List<DisplayableTweet>) fun showEmpty() fun showLoading() .... } Presentation
  • 17.
    class TweetFeedPresenter @Injectconstructor(
 private val refreshFeedInteractor: RefreshFeedInteractor,
 private val observeFeedInteractor: ObserveFeedInteractor,
 private val mapToDisplayableItem: Mapper<DisplayableItem>
 ) : TweetFeedContract.Presenter { private val disposables: CompositeDisposable
 ... } Presentation
  • 18.
    override fun onRefresh(){
 refreshFeedInteractor.excecute()
 } TweetFeedPresenter
  • 19.
    override fun onAttach(view:TweetFeedContract.View) {
 this.view = view
 disposables += bindToFeed()
 } TweetFeedPresenter
  • 20.
    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
  • 21.
    Models should makesense in the context where they are being used.
  • 22.
    What the map? publicclass Item { private String validTo;
 private float price; private String validFrom; private int type; }
  • 23.
    What the map? publicclass Item { @SerializedName ("validto") private String validTo; @SerializedName ("price")
 private float price; @SerializedName ("validfrom") private String validFrom; @SerializedName (value = "type") private int type; }
  • 24.
    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; }
  • 25.
    What the map? publicclass 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; }
  • 26.
    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; }
  • 27.
    What the map DataPresentationDomain Model PresenterView Interactor Model Operation API Repository Cache DBAbstraction
  • 28.
    What the map DataPresentationDomain Presentation
 Model PresenterView Interactor Domain
 Model Operation API Repository Cache DBAbstraction API
 Model Cache
 Model
  • 29.
    Domain data flow D A T A execute() domainDatadomainData 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()
  • 30.
    Interactors abstract class CompletableInteractor<inParams>( 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() }
  • 31.
    Interactors class RefreshFeedInteractor @Injectconstructor( private val tweetRepository: TweetRepository, executionScheduler: Scheduler, postExecutionScheduler: Scheduler) : CompletableInteractor<FeedSpec>(executionScheduler, postExecutionScheduler) { override fun buildInteractorCompletable(params: FeedSpec): Completable = tweetRepository.refreshFeed(params) }
  • 32.
    Interactors abstract class ObservableInteractor<T,in Params>( private val executionScheduler: Scheduler, private val postExecutionScheduler: Scheduler) { abstract fun buildInteractorCompletable(params: Params): Observable<T> fun execute(params: Params): Observable<T> = buildInteractorObservable(params) .subscribeOn(executionScheduler) .observeOn(postExecutionScheduler) .subscribe() }
  • 33.
    Interactors class RefreshFeedInteractor @Injectconstructor( private val tweetListRepository: TweetListRepository, executionScheduler: Scheduler, postExecutionScheduler: Scheduler) : ObservableInteractor<List<Tweet>, FeedSpec>(executionScheduler, postExecutionScheduler) { override fun buildInteractorCompletable(params: FeedSpec): Observable<List<Tweet>> = tweetListRepository.observeFeed(params) } .filter { it.createdDate.after(someDate) } .compose { customTransformer }
  • 34.
    interface TweetListRepository { funrefreshFeed(feedSpec: FeedSpec): Completable fun observeFeed(feedSpec: FeedSpec): Observable<List<Tweet>> }
  • 35.
    class ReactiveTwitterRepository( private valapiMapper: ApiToStoreMapper, 
 private val api: SomeApi, 
 private val store: ReactiveStore) : TweetRepository { override fun refreshFeed(): Completable =
 api.retrieveTweetList()
 .map { apiMapper }
 .doOnSuccess { store.storeTweets(it) }
 .toCompletable() ... }
  • 36.
    class ReactiveTwitterRepository( private valapiMapper: ApiToStoreMapper, 
 private val domainMapper: StoreToDomainMapper, 
 private val api: SomeApi, 
 private val store: ReactiveStore) : TweetRepository { ... override fun getFeedObservable(feedSpec: FeedSpec):
 Observable<List<Tweet> =
 store.getFeedObservable()
 .map(domainMapper) ... }
  • 37.
    interface ReactiveStore<Key, Value>{ fun storeSingle(item: Value): Completable fun storeAll(items: List<Value>): Completable fun replaceAll(items: List<Value>): Completable fun getSingle(key: Key): Observable<Value> fun getAll(): Observable<List<Value>> } ReactiveStore
  • 38.
    ReactiveStore ! Coordinates puttingdata in persistence and/or cache ! Create, store and feed the stream ! Implementation depends on the application ! Single source of truth
  • 39.
  • 40.
    Final advices ! Bepragmatic ○ 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!
  • 41.