v1.3.0
Version 1.3.0 is a functional and async version of fmodel
libraries optimized for Event sourcing and CQRS.
Please find the README/DOC here. It is maintained on the v1 branch.
Version
2.*.*
is a reactive version (Flow included) of the libraries optimized for Event sourcing and CQRS. It is maintained on the main branch
What's Changed
- Increasing modularity and pluggability of the Application module
The application module has interfaces only.
The extensions to the interfaces will be provided in separate modules:- application vanilla extension (kotlin),
- application arrow extension (kotlin, arrow),
- your custom module
Application
interface EventSourcingAggregate<C, S, E> : IDecider<C, S, E>, EventRepository<C, E> {
fun Sequence<E>.computeNewEvents(command: C): Sequence<E> =
decide(command, fold(initialState) { s, e -> evolve(s, e) })
}
Application Vanilla
suspend fun <C, S, E> EventSourcingAggregate<C, S, E>.handle(command: C): Sequence<E> =
command
.fetchEvents()
.computeNewEvents(command)
.save()
Application Arrow
suspend fun <C, S, E> EventSourcingAggregate<C, S, E>.handleEither(command: C): Either<Error, Sequence<E>> {
suspend fun C.eitherFetchEventsOrFail(): Either<Error.FetchingEventsFailed, Sequence<E>> =
Either.catch {
fetchEvents()
}.mapLeft { throwable -> Error.FetchingEventsFailed(throwable) }
suspend fun E.eitherSaveOrFail(): Either<Error.StoringEventFailed<E>, E> =
Either.catch {
this.save()
}.mapLeft { throwable -> Error.StoringEventFailed(this, throwable) }
suspend fun Sequence<E>.eitherSaveOrFail(): Either<Error.StoringEventFailed<E>, Sequence<E>> =
either<Error.StoringEventFailed<E>, List<E>> {
this@eitherSaveOrFail.asIterable().map { it.eitherSaveOrFail().bind() }
}.map { it.asSequence() }
fun Sequence<E>.eitherComputeNewEventsOrFail(command: C): Either<Error, Sequence<E>> =
Either.catch {
computeNewEvents(command)
}.mapLeft { throwable ->
Error.CalculatingNewEventsFailed(this.toList(), throwable)
}
// Arrow provides a Monad instance for Either. Except for the types signatures, our program remains unchanged when we compute over Either. All values on the left side assume to be Right biased and, whenever a Left value is found, the computation short-circuits, producing a result that is compatible with the function type signature.
return either {
command
.eitherFetchEventsOrFail().bind()
.eitherComputeNewEventsOrFail(command).bind()
.eitherSaveOrFail().bind()
}
}