Skip to main content

Application

The only responsibility of the application layer is to orchestrate the execution of the logic by

  • loading the state from a repository
  • execute logic by executing domain layer components
  • store the new state

Fmodel offers application interfaces/components which are actually composed out of repository interfaces/components (side effects) and core domain components (decision-making):

Event-Sourced vs State-Stored

Event-stored systems are split to command and view/query models, by default. This is making a huge difference as we are not limited to use a single canonical model for writing and reading/presenting!

event-modeling-event-driven-systems

Event-sourcing Aggregate is a formalization of the event-stored/event-sourced system (Command Model) mentioned previously.

interface EventSourcingAggregate<C, S, E> : IDecider<C, S, E>, EventRepository<C, E>
interface EventSourcingOrchestratingAggregate<C, S, E> : IDecider<C, S, E>, EventRepository<C, E>, ISaga<E, C>
interface MaterializedView<S, E> : IView<S, E>, ViewStateRepository<E, S>
interface SagaManager<AR, A> : ISaga<AR, A>, ActionPublisher<A>

Event-sourcing Aggregate is using/delegating a Decider to handle commands and produce events. It belongs to the Application layer. In order to handle the command, aggregate needs to fetch the current state (represented as a list of events) via EventRepository.fetchEvents function, and then delegate the command to the decider which can produce new events as a result. Produced events are then stored via EventRepository.save suspending function.

The Delegation pattern has proven to be a good alternative to implementation inheritance, and Kotlin supports it natively requiring zero boilerplate code. eventSourcingAggregate and eventSourcingOrchestratingAggregate functions, provided by the Fmodel, are good example:

fun <C, S, E> eventSourcingAggregate(
decider: IDecider<C, S, E>,
eventRepository: EventRepository<C, E>
): EventSourcingAggregate<C, S, E> =
object :
EventSourcingAggregate<C, S, E>,
EventRepository<C, E> by eventRepository,
IDecider<C, S, E> by decider {}

fun <C, S, E> eventSourcingOrchestratingAggregate(
decider: IDecider<C, S, E>,
eventRepository: EventRepository<C, E>,
saga: ISaga<E, C>
): EventSourcingOrchestratingAggregate<C, S, E> =
object : EventSourcingOrchestratingAggregate<C, S, E>,
EventRepository<C, E> by eventRepository,
IDecider<C, S, E> by decider,
ISaga<E, C> by saga {}

Example of a monolith scenario, in which Order and Restaurant deciders are combined/aggregated in one big decider and then wrapped by one aggregate component:

/**
* A convenient type alias for Decider<OrderCommand?, Order?, OrderEvent?>
*/
typealias OrderDecider = Decider<OrderCommand?, Order?, OrderEvent?>

/**
* A convenient type alias for Decider<RestaurantCommand?, Restaurant?, RestaurantEvent?>
*/
typealias RestaurantDecider = Decider<RestaurantCommand?, Restaurant?, RestaurantEvent?>

/**
* A convenient type alias for EventRepository<Command?, Event?>
*
* notice that OrderCommand and RestaurantCommand are extending `sealed` Command,
* and that OrderEvent and RestaurantEvent are extending `sealed` Event
*/
typealias AggregateEventRepository = EventRepository<Command?, Event?>


val aggregate = eventSourcingAggregate(orderDecider combine restaurantDecider, aggregateEventRepository)


/**
* Start handling all your commands!
*/
aggregate.handle(command)

Materialized View is a formalization of the event-stored/event-sourced system (View Model) mentioned previously.

interface MaterializedView<S, E> : IView<S, E>, ViewStateRepository<E, S>

Materialized view is using/delegating a View (domain component) to handle events of type E and to maintain a state of denormalized projection(s) as a result.

interface MaterializedView<S, E> : IView<S, E>, ViewStateRepository<E, S>

// Notice the `delegation pattern`
fun <S, E> materializedView(
view: IView<S, E>,
viewStateRepository: ViewStateRepository<E, S>,
): MaterializedView<S, E> =
object : MaterializedView<S, E>, ViewStateRepository<E, S> by viewStateRepository, IView<S, E> by view {}

Example of a monolith scenario, in which Order and Restaurant views are combined in one big view and then wrapped by one materialized-view component:

/**
* A convenient type alias for View<OrderViewState?, OrderEvent?>
*/
typealias OrderView = View<OrderViewState?, OrderEvent?>

/**
* A convenient type alias for View<RestaurantViewState?, RestaurantEvent?>
*/
typealias RestaurantView = View<RestaurantViewState?, RestaurantEvent?>

/**
* A convenient type alias for ViewStateRepository<OrderEvent?, Pair<OrderViewState?, RestaurantViewState?>>
*/
typealias MaterializedViewStateRepository = ViewStateRepository<Event?, Pair<OrderViewState?, RestaurantViewState?>>


val materializedView = materializedView(orderView combine restaurantView, materializedViewStateRepository)


/**
* Start handling all your events, and projecting them into denormalized state which is adequate for querying.
*/
materializedView.handle(event)

Application modules

The application modules are organized in hierarchy:

  • application - base module is declaring all application interfaces: aggregate, materialized-view, saga-manager.
  • extensions - extension modules are extending the base module by providing concrete implementation of the handle method as an extension function(s).
    • application-vanilla - is using plain/vanilla Kotlin to implement the application layer in order to load the state, orchestrate the execution of the logic and save new state.
    • application-arrow - is using Arrow and Kotlin to implement the application layer in order to load the state, orchestrate the execution of the logic and save new state - providing structured, predictable and efficient handling of errors (using Either).
info

The libraries are non-intrusive, and you can select any flavor, choose both (vanilla and/or arrow) or make your own extension.

You can use only domain library and model the orchestration (application library) on your own.

An example (taken from FModel application-vanilla library):

fun <C, S, E> EventSourcingAggregate<C, S, E>.handle(command: C): Flow<E> =
command
.fetchEvents()
.computeNewEvents(command)
.save()

Feel free to use these two extension modules, or create your own by using these two as a fine example.