Aggregating the Behaviour
The combine
is a binary operation over the decider
, view
and saga
, satisfying associativity and having an
identity/empty element.
Associativity facilitates parallelization by giving us the freedom to break problems into chunks that can be computed in parallel.
Functional paradigm and category theory define this algebra as a Monoid
.
Stated tersely, a monoid
is a type together with a binary operation (combine
) over that type, satisfying
associativity
and having an identity element (zero/empty).
combine
operation is also commutative / commutative monoid
- Decider
- View
- Saga
associative: (decider1 + decider2) + decider3 = decider1 + (decider2 + decider3)
commutative: decider1 + decider2 = decider2 + decider1
zero: decider1 + decider0 = decider1
By combining two or more deciders you get the new decider.
This is a formal signature of the combine
extension function defined on the decider
:
inline infix fun <reified C1 : C_SUPER, S1, reified E1 : E_SUPER, reified C2 : C_SUPER, S2, reified E2 : E_SUPER, C_SUPER, E_SUPER> Decider<C1?, S1, E1?>.combine(
y: Decider<C2?, S2, E2?>
): Decider<C_SUPER, Pair<S1, S2>, E_SUPER>
Type parameters are restricted by generic constraints. Notice the upper
bounds C1 : C_SUPER
, E1 : E_SUPER
, C2 : C_SUPER
, E2 : E_SUPER
.
It is only possible to use the combine
function when:
E1
andE2
have common superclassE_SUPER
C1
andC2
have common superclassC_SUPER
C1?
,C2?
,E1?
andE2?
are nullable.
val orderDecider: Decider<OrderCommand?, Order?, OrderEvent?> = orderDecider()
val restaurantDecider: Decider<RestaurantCommand?, Restaurant?, RestaurantEvent?> = restaurantDecider()
// Combining two deciders into one big decider that can handle all commands of the system.
val allDecider: Decider<Command?, Pair<Order?, Restaurant?>, Event?> = orderDecider combine restaurantDecider
If the constraints are not met, the combine
function will not be available for usage!
Mappable
Additionally, Decider<C, S, E>
provides map functions:
inline fun <Cn> mapLeftOnCommand(
crossinline f: (Cn) -> C
): Decider<Cn, S, E>
inline fun <En> dimapOnEvent(
crossinline fl: (En) -> E, crossinline fr: (E) -> En
): Decider<C, S, En>
inline fun <Sn> dimapOnState(
crossinline fl: (Sn) -> S, crossinline fr: (S) -> Sn
): Decider<C, Sn, E>
For example the allDecider
of type Decider<Command?, Pair<Order?, Restaurant?>, Event?>
can be mapped into a newAllDecider
of type Decider<Command?, MyDomainSpecificType, Event?>
, effectively eliminating the Pair
:
val newAllDecider = allDecider.dimapOnState(
fl = { myDomainSpecificType: MyDomainSpecificType -> Pair(myDomainSpecificType.order, myDomainSpecificType.restaurant) },
fr = { pair: Pair<Order?, Restaurant?> -> MyDomainSpecificType(pair.first, pair.second) }
)
associative: (view1 + view2) + view3 = view1 + (view2 + view3)
commutative: view1 + view2 = view2 + view1
zero: view1 + view0 = view1
By combining two or more views you get the new view.
This is a formal signature of the combine
extension function defined on the view
:
inline infix fun <Sx, reified Ex : E_SUPER, Sy, reified Ey : E_SUPER, E_SUPER> View<Sx, Ex?>.combine(y: View<Sy, Ey?>): View<Pair<Sx, Sy>, E_SUPER>
Type parameters are restricted by generic constraints. Notice the upper bounds Ex : E_SUPER
, Ey : E_SUPER
.
It is only possible to use the combine
function when:
Ex
andEy
have common superclassE_SUPER
Ex?
andEy?
are nullable.
val orderView: View<OrderViewState?, OrderEvent?> = orderView()
val restaurantView: View<RestaurantViewState?, RestaurantEvent?> = restaurantView()
// Combining two views into one big view that can handle all events of the system.
val allView: View<Pair<OrderViewState?, RestaurantViewState?>, Event?> = orderView combine restaurantView
If the constraints are not met, the combine
function will not be available for usage!
Mappable
Additionally, View<S, E>
provides map functions:
inline fun <En> mapLeftOnEvent(
crossinline f: (En) -> E
): View<S, En>
inline fun <Sn> dimapOnState(
crossinline fl: (Sn) -> S,
crossinline fr: (S) -> Sn
): View<Sn, E>
For example the allView
of type View<Pair<OrderViewState?, RestaurantViewState?>, Event?>
can be mapped into a newAllView
of type View<MyDomainSpecificType, Event?>
, effectively eliminating the Pair
:
val newAllView = allView.dimapOnState(
fl = { myDomainSpecificType: MyDomainSpecificType -> Pair(myDomainSpecificType.order, myDomainSpecificType.restaurant) },
fr = { pair: Pair<OrderViewState?, RestaurantViewState?> -> MyDomainSpecificType(pair.first, pair.second) }
)
associative: (saga1 + saga2) + saga3 = saga1 + (saga2 + saga3)
commutative: saga1 + saga2 = saga2 + saga1
zero: saga1 + saga0 = saga1
By combining two or more sagas you get the new saga.
This is a formal signature of the combine
extension function defined on the saga
:
inline infix fun <reified ARx : AR_SUPER, Ax : A_SUPER, reified ARy : AR_SUPER, Ay : A_SUPER, AR_SUPER, A_SUPER> Saga<in ARx?, out Ax>.combine(
y: Saga<in ARy?, out Ay>
): Saga<AR_SUPER, A_SUPER>
Type parameters are restricted by generic constraints. Notice the upper
bounds ARx : AR_SUPER
, ARy : AR_SUPER
, Ax : A_SUPER
, Ay : A_SUPER
.
It is only possible to use the combine
function when:
ARx
andARy
have common superclassAR_SUPER
Ax
andAy
have common superclassA_SUPER
ARx?
andARy?
are nullable.
val orderSaga: Saga<RestaurantEvent?, OrderCommand> = orderSaga()
val restaurantSaga: Saga<OrderEvent?, RestaurantCommand> = restaurantSaga()
// Combining two choreography sagas into one big system orchestrating saga.
val allSaga: Saga<Event?, Command> = orderSaga combine restaurantSaga
If the constraints are not met, the combine
function will not be available for usage!
Mappable
Additionally, Saga<AR, A>
provides map functions:
inline fun <ARn> mapLeftOnActionResult(crossinline f: (ARn) -> AR): Saga<ARn, A>
inline fun <An> mapOnAction(crossinline f: (A) -> An): Saga<AR, An>