Share
Embed
Reactive Workflows Update
A preview of Square Workflow, presented at droidconNYC 2018.
https://www.youtube.com/watch?v=cw9ZF9-ilac
Original deck: https://docs.google.com/presentation/d/1tn4CxpoZHADxvf0mgJ57Yt7VQBCQVPpMKaM4vSDxSbU/edit#slide=id.g4088c7565d_2_5
https://github.com/square/workflow/
rjrjr
August 28, 2018
Transcript
99 slides
-
Immutability is assumed Be reactive: push, don’t pull Natural separation
of UI and business concerns Composition or go home Cardinal Architecture Virtues -
Container Rendering, Event Handling Business Objects CRUD Workflow Navigation, View
Model NewGame PlayGame QuitGame GameOver -
Business Objects CRUD State View Model Event Update Workflow Navigation,
View Model NewGame PlayGame QuitGame GameOver Container Rendering, Event Handling -
val bar: BarValue fun setFoo(f: FooValue) fun setBar(b: BarValue) fun
setBaz(b: Bazvalue) val bang: Observable<BangValue> val buzz: Observable<BuzzValue> -
fun perform(op: Operation) sealed class Operation { class Update(vararg stuff:
Stuff) : Operation() class Delete(vararg ids: Int) : Operation() } val stuffs: Observable<List<Stuff>> -
Container Rendering, Event Handling Business Objects CRUD Workflow Navigation, View
Model NewGame PlayGame QuitGame GameOver State View Model Event Update -
Play Game Playing( X or O ) Completed( X, O,
draw or quit ) -
Play Game Playing( Symbol ) Completed( Ending ) Symbol {
X, O, None } Ending { Victor(Symbol) Draw Quitter(Symbol) } -
Play Game Playing( Symbol ) Completed( Ending ) Event {
TakeSquare(row, col) Quit } Symbol { X, O, None } Ending { Victor(Symbol) Draw Quitter(Symbol) } -
Play Game Playing( Turn ) Completed( Ending, Turn ) typealias
Board = List<List<Symbol>> data class Turn( val players: Map<Symbol, String>, val playing: Symbol = X, val board: Board = EMPTY_BOARD ) -
interface Workflow<out S : Any, out O : Any> {
val state: Observable<out S> val result: Single<out O> fun abandon() } -
class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { sealed class Event
{ class TakeSquare( val row: Int, val col: Int ) : Event() object Quit : Event() } private val events = PublishRelay.create<Event>() fun sendEvent(e: Event) = events.call(e) -
class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { sealed class Event
{ class TakeSquare( val row: Int, val col: Int ) : Event() object Quit : Event() } private val events = PublishRelay.create<Event>() fun sendEvent(e: Event) = events.call(e) -
class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { sealed class State
{ abstract val turn: Turn data class Playing( override val turn: Turn ) : State() data class Completed( val ending: Ending, override val turn: Turn ) : State() } data class Turn( val players: Map<Symbol, String>, val playing: Symbol = X, val board: Board = EMPTY_BOARD ) -
class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { sealed class State
{ abstract val turn: Turn data class Playing( override val turn: Turn ) : State() data class Completed( val ending: Ending, override val turn: Turn ) : State() } -
class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { sealed class State
{ abstract val turn: Turn data class Playing( override val turn: Turn ) : State() data class Completed( val ending: Ending, override val turn: Turn ) : State() } sealed class Ending { object Victor : Ending() object Draw : Ending() object Quitter : Ending() } -
class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { private val states
= BehaviorRelay.create(Single.just(initial)) private val _state: ConnectableObservable<State> = states .switchMap { it.toObservable() } .doOnNext { currentState -> states.call(nextState(currentState)) } .replay(1) private fun nextState(fromState: State): Single<State> = when (fromState) { is Completed -> Observable.never<State>() is Playing -> events.first().map { event -> when (event) { is TakeSquare -> takeSquareAndNextState(fromState.turn, event.row, event.col) Quit -> Completed(Quitter, fromState.turn) } } }.toSingle() -
class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { private val states
= BehaviorRelay.create(Single.just(initial)) private val _state: ConnectableObservable<State> = states .switchMap { it.toObservable() } .doOnNext { currentState -> states.call(nextState(currentState)) } .replay(1) private fun nextState(fromState: State): Single<State> = when (fromState) { is Completed -> Observable.never<State>() is Playing -> events.first().map { event -> when (event) { is TakeSquare -> takeSquareAndNextState(fromState.turn, event.row, event.col) Quit -> Completed(Quitter, fromState.turn) } } }.toSingle() -
class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { private val states
= BehaviorRelay.create(Single.just(initial)) private val _state: ConnectableObservable<State> = states .switchMap { it.toObservable() } .doOnNext { currentState -> states.call(nextState(currentState)) } .replay(1) private fun nextState(fromState: State): Single<State> = when (fromState) { is Completed -> Observable.never<State>() is Playing -> events.first().map { event -> when (event) { is TakeSquare -> takeSquareAndNextState(fromState.turn, event.row, event.col) Quit -> Completed(Quitter, fromState.turn) } } }.toSingle() -
class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { private val states
= BehaviorRelay.create(Single.just(initial)) private val _state: ConnectableObservable<State> = states .switchMap { it.toObservable() } .doOnNext { currentState -> states.call(nextState(currentState)) } .replay(1) private fun nextState(fromState: State): Single<State> = when (fromState) { is Completed -> Observable.never<State>() is Playing -> events.first().map { event -> when (event) { is TakeSquare -> takeSquareAndNextState(fromState.turn, event.row, event.col) Quit -> Completed(Quitter, fromState.turn) } } }.toSingle() -
class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { private val states
= BehaviorRelay.create(Single.just(initial)) private val _state: ConnectableObservable<State> = states .switchMap { it.toObservable() } .doOnNext { currentState -> states.call(nextState(currentState)) } .replay(1) private fun nextState(fromState: State): Single<State> = when (fromState) { is Completed -> Observable.never<State>() is Playing -> events.first().map { event -> when (event) { is TakeSquare -> takeSquareAndNextState(fromState.turn, event.row, event.col) Quit -> Completed(Quitter, fromState.turn) } } }.toSingle() -
class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { private val states
= BehaviorRelay.create(Single.just(initial)) private val _state: ConnectableObservable<State> = states .switchMap { it.toObservable() } .doOnNext { currentState -> states.call(nextState(currentState)) } .replay(1) private fun nextState(fromState: State): Single<State> = when (fromState) { is Completed -> Observable.never<State>() is Playing -> events.first().map { event -> when (event) { is TakeSquare -> takeSquareAndNextState(fromState.turn, event.row, event.col) Quit -> Completed(Quitter, fromState.turn) } } }.toSingle() -
class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { private val states
= BehaviorRelay.create(Single.just(initial)) private val _state: ConnectableObservable<State> = states .switchMap { it.toObservable() } .doOnNext { currentState -> states.call(nextState(currentState)) } .replay(1) private fun nextState(fromState: State): Single<State> = when (fromState) { is Completed -> Observable.never<State>() is Playing -> events.first().map { event -> when (event) { is TakeSquare -> takeSquareAndNextState(fromState.turn, event.row, event.col) Quit -> Completed(Quitter, fromState.turn) } } }.toSingle() -
class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { private val states
= BehaviorRelay.create(Single.just(initial)) private val _state: ConnectableObservable<State> = states .switchMap { it.toObservable() } .doOnNext { currentState -> states.call(nextState(currentState)) } .replay(1) override val state: Observable<out Turn> = _state.takeWhile { it !is Completed } .map { it.turn } override val result: Single<out Completed> = _state.ofType<Completed>() .first() .toSingle() .cache() -
class PlayGameWorkflow(initial: State) : Workflow<Turn, Completed> { private fun nextState(fromState:
State): Single<State> = when (fromState) { is Completed -> Observable.never<State>() is Playing -> events.first().map { event -> when (event) { is TakeSquare -> takeSquareAndNextState(fromState.turn, event.row, event.col) Quit -> Completed(Quitter, fromState.turn) } } }.toSingle() -
class PlayGameReactor : Reactor<Playing, Event, Completed>() { override fun onReact(fromState:
Playing): Single<out Reaction<Playing, Completed>> = selectEvent { onEvent<TakeSquare> { EnterState(takeSquareAndNextState(fromState.turn, it.row, it.col)) } onEvent<Quit> { FinishWith(Completed(Quitter, fromState.turn)) } } -
class PlayGameReactor : Reactor<Playing, Event, Completed>() { override fun onReact(fromState:
Playing): Single<out Reaction<Playing, Completed>> = selectEvent { onEvent<TakeSquare> { EnterState(takeSquareAndNextState(fromState.turn, it.row, it.col)) } onEvent<Quit> { FinishWith(Completed(Quitter, fromState.turn)) } } -
class PlayGameReactor : Reactor<Playing, Event, Completed>() { override fun onReact(fromState:
Playing): Single<out Reaction<Playing, Completed>> = selectEvent { onEvent<TakeSquare> { EnterState(takeSquareAndNextState(fromState.turn, it.row, it.col)) } onEvent<Quit> { FinishWith(Completed(Quitter, fromState.turn)) } } -
class PlayGameReactor : Reactor<Playing, Event, Completed>() { override fun onReact(fromState:
Playing): Single<out Reaction<Playing, Completed>> = selectEvent { onEvent<TakeSquare> { EnterState(takeSquareAndNextState(fromState.turn, it.row, it.col)) } onEvent<Quit> { FinishWith(Completed(Quitter, fromState.turn)) } } -
class PlayGameReactor : Reactor<Playing, Event, Completed>() { typealias PlayGameWorkflow =
ReactorWorkflow<Playing, Event, Completed> companion object { fun startWorkflow( x: String, o: String ): PlayGameWorkflow = PlayGameReactor().asWorkflow(Playing(Turn(x, o))) } -
class PlayGameReactor : Reactor<Playing, Event, Completed>() { typealias PlayGameWorkflow =
ReactorWorkflow<Playing, Event, Completed> companion object { fun startWorkflow( x: String, o: String ): PlayGameWorkflow = PlayGameReactor().asWorkflow(Playing(Turn(x, o))) fun resumeWorkflow(turn: Turn): PlayGameWorkflow = PlayGameReactor().asWorkflow(Playing(turn)) } -
class GameAppReactor : Reactor<State, Event, Unit>() { sealed class State
{ class NewGame( val defaultXName: String = "X", val defaultOName: String = "O" ) : State() class Playing(val workflow: PlayGameWorkflow) : State() class MaybeQuitting(val completedGame: Completed) : State() class GameOver(val completedGame: Completed) : State() } -
class GameAppReactor : Reactor<State, Event, Unit>() { sealed class Event
{ class StartGame( val x: String, val o: String ) : Event() object ConfirmQuit : Event() object ContinuePlaying : Event() object StartNewGame : Event() object QuitApp : Event() } -
class GameAppReactor : Reactor<State, Event, Unit>() { override fun onReact(state:
State): Single<out Reaction<State, Unit>> = when (state) { -
class GameAppReactor : Reactor<State, Event, Unit>() { override fun onReact(state:
State): Single<out Reaction<State, Unit>> = when (state) { is NewGame -> selectEvent { onEvent<QuitApp> { FinishWith(Unit) } onEvent<StartGame> { EnterState(Playing(PlayGameReactor.startWorkflow(it.x, it.o))) } } -
class GameAppReactor : Reactor<State, Event, Unit>() { override fun onReact(state:
State): Single<out Reaction<State, Unit>> = when (state) { is NewGame -> selectEvent { onEvent<QuitApp> { FinishWith(Unit) } onEvent<StartGame> { EnterState(Playing(PlayGameReactor.startWorkflow(it.x, it.o))) } } -
class GameAppReactor : Reactor<State, Event, Unit>() { override fun onReact(state:
State): Single<out Reaction<State, Unit>> = when (state) { is NewGame -> selectEvent { onEvent<QuitApp> { FinishWith(Unit) } onEvent<StartGame> { EnterState(Playing(PlayGameReactor.startWorkflow(it.x, it.o))) } } is Playing -> state.workflow.result.map { result -> when (result.ending) { Quitter -> EnterState(MaybeQuitting(result)) else -> EnterState(GameOver(result)) } } -
class GameAppReactor : Reactor<State, Event, Unit>() { override fun onReact(state:
State): Single<out Reaction<State, Unit>> = when (state) { is Playing -> state.workflow.result.map { result -> when (result.ending) { Quitter -> EnterState(MaybeQuitting(result)) else -> EnterState(GameOver(result)) } } is MaybeQuitting -> selectEvent { onEvent<ConfirmQuit> { EnterState(GameOver(state.completedGame)) } onEvent<ContinuePlaying> { EnterState(Playing(PlayGameReactor.resumeWorkflow(state.completedGame.turn))) } } -
class GameAppReactor : Reactor<State, Event, Unit>() { override fun onReact(state:
State): Single<out Reaction<State, Unit>> = when (state) { is Playing -> state.workflow.result.map { result -> when (result.ending) { Quitter -> EnterState(MaybeQuitting(result)) else -> EnterState(GameOver(result)) } } is MaybeQuitting -> selectEvent { onEvent<ConfirmQuit> { EnterState(GameOver(state.completedGame)) } onEvent<ContinuePlaying> { EnterState(Playing(PlayGameReactor.resumeWorkflow(state.completedGame.turn))) } } -
class GameAppReactor : Reactor<State, Event, Unit>() { override fun onReact(state:
State): Single<out Reaction<State, Unit>> = when (state) { is Playing -> state.workflow.result.map { result -> when (result.ending) { Quitter -> EnterState(MaybeQuitting(result)) else -> EnterState(GameOver(result)) } } is MaybeQuitting -> selectEvent { onEvent<ConfirmQuit> { EnterState(GameOver(state.completedGame)) } onEvent<ContinuePlaying> { EnterState(Playing(PlayGameReactor.resumeWorkflow(state.completedGame.turn))) } } -
Game Service Playing(PlayGameWorkflow) NewGame MaybeQuitting(Completed) ConfirmQuit Continue Playing Quitter !Quitter
StartNewGame GameOver(Completed) StartNewGame -
Game Service Playing(PlayGameWorkflow) NewGame GameOver( Completed, SyncState ) MaybeQuitting(Completed) ConfirmQuit
Continue Playing Quitter !Quitter StartNewGame StartNewGame -
override fun onReact(state: State): Single<out Reaction<State, Unit>> = when (state)
{ // Left as an exercise for the reader. // // Hint: Retrofit requests are rx.Single<*> -
Container Rendering, Event Handling Business Objects CRUD Workflow Navigation, View
Model NewGame PlayGame QuitGame GameOver State View Model Event Update -
/** * @param data A complete rendering of this screen.
* @param eventHandler The object that accepts events for this screen. */ class Screen<D : Any, E : Any>( val data: D, val eventHandler: E ) -
/** * @param data A complete rendering of this screen.
* @param eventHandler The object that accepts events for this screen. */ class Screen<D : Any, E : Any>( val data: D, val eventHandler: E ) typealias AnyScreen = Screen<*, *> -
/** * @param data A complete rendering of this screen.
* @param eventHandler The object that accepts events for this screen. */ class Screen<D : Any, E : Any>( val data: D, val eventHandler: E ) typealias AnyScreen = Screen<*, *> /** * A [Workflow] that exposes its state as a stream * of [screen models][Screen]. */ typealias ScreenWorkflow<O> = Workflow<AnyScreen, O> -
class GameAppScreens(private val wrapped: GameAppWorkflow) : ScreenWorkflow<Unit> { override val
state: Observable<out AnyScreen> = wrapped.state.switchMap { when (it) { is Playing -> it.workflow.state.map { playing -> Screen(playing.turn, it.workflow) } else -> just(Screen(it, wrapped)) } } override val result: Single<out Unit> = wrapped.result override fun abandon() = wrapped.abandon() } -
class GameAppScreens(private val wrapped: GameAppWorkflow) : ScreenWorkflow<Unit> { override val
state: Observable<out AnyScreen> = wrapped.state.switchMap { when (it) { is Playing -> it.workflow.state.map { playing -> Screen(playing.turn, it.workflow) } else -> just(Screen(it, wrapped)) } } override val result: Single<out Unit> = wrapped.result override fun abandon() = wrapped.abandon() } -
class GameAppScreens(private val wrapped: GameAppWorkflow) : ScreenWorkflow<Unit> { override val
state: Observable<out AnyScreen> = wrapped.state.switchMap { when (it) { is Playing -> it.workflow.state else -> just(Screen(it, wrapped)) } } override val result: Single<out Unit> = wrapped.result override fun abandon() = wrapped.abandon() } -
public struct ViewRegistry<View> { // ... public func provideView<T: Screen>(for
screen: T) -> View // ... } The View Registry turns screen models into live views. -
public struct ViewRegistry<View> { // ... public func provideView<T: Screen>(for
screen: T) -> View // Registers a factory block for the screen type `T`. public mutating func register<T: Screen>( screenType: T.Type, factory: @escaping (Observable<T>, ViewRegistry<View>) -> View // ... } The View Registry requires screens to be registered. 73 -
public struct ViewRegistry<View> { // ... public func provideView<T: Screen>(for
screen: T) -> View // Registers a factory block for the screen type `T`. public mutating func register<T: Screen>( screenType: T.Type, factory: @escaping (Observable<T>, ViewRegistry<View>) -> View // ... } The View Registry is recursive. 74 -
public struct BackStackScreen: Screen { public var currentItem: Item public
struct Item { public var screen: Screen public var key: String public var barContent: NavigationBarContent } } -
public struct ModalContainerScreen: Screen { public var main: Screen public
var modal: Screen? } 89 -
square.com Ray Ryan / octodon.social/@rjrjr Timothy Donnelly / twitter.com/tdonnelly github.com/JakeWharton/RxRelay
github.com/square/coordinators