Skip to main content

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

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 and E2 have common superclass E_SUPER
  • C1 and C2 have common superclass C_SUPER
  • C1?, C2?, E1? and E2? 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) }
)