From dff3471d86a04a2d10f2bb12571c6beffa7f8c4b Mon Sep 17 00:00:00 2001 From: Ivan Dugalic Date: Sat, 5 Mar 2022 19:09:28 +0100 Subject: [PATCH 01/13] Prototype of Context Receivers - JVM only --- application-vanilla/build.gradle.kts | 1 + ...entSourcingAggregateContextualExtension.kt | 123 ++++++++++++++++++ .../MaterializedViewContextualExtension.kt | 53 ++++++++ .../SagaManagerContextualExtension.kt | 54 ++++++++ ...StateStoredAggregateContextualExtension.kt | 118 +++++++++++++++++ gradle/libs.versions.toml | 2 +- 6 files changed, 350 insertions(+), 1 deletion(-) create mode 100644 application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateContextualExtension.kt create mode 100644 application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt create mode 100644 application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerContextualExtension.kt create mode 100644 application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualExtension.kt diff --git a/application-vanilla/build.gradle.kts b/application-vanilla/build.gradle.kts index bbd7d693..6ac9e9be 100644 --- a/application-vanilla/build.gradle.kts +++ b/application-vanilla/build.gradle.kts @@ -12,6 +12,7 @@ kotlin { compilations.all { kotlinOptions.jvmTarget = "1.8" kotlinOptions.verbose = true + kotlinOptions.freeCompilerArgs = kotlinOptions.freeCompilerArgs + "-Xcontext-receivers" } withJava() diff --git a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateContextualExtension.kt b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateContextualExtension.kt new file mode 100644 index 00000000..c002b7e0 --- /dev/null +++ b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateContextualExtension.kt @@ -0,0 +1,123 @@ +package com.fraktalio.fmodel.application + +import com.fraktalio.fmodel.domain.IDecider +import com.fraktalio.fmodel.domain.ISaga +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.* + +/** + * Event-sourced aggregate/decider algorithm + * Computes new Events based on the previous Events and the Command. + */ +context (IDecider, C) + internal fun Flow.computeNewEvents(): Flow = flow { + val currentState = fold(initialState) { s, e -> evolve(s, e) } + val resultingEvents = decide(this@C, currentState) + emitAll(resultingEvents) +} + +/** + * Event-sourced orchestrating aggregate/decider algorithm + * Computes new Events based on the previous Events and the Command. + * Saga might react on Events and send new Commands to the Decider. + */ +context (IDecider, ISaga, C) + @FlowPreview + internal fun Flow.computeNewEvents(): Flow = flow { + val currentState = fold(initialState) { s, e -> evolve(s, e) } + var resultingEvents = decide(this@C, currentState) + + resultingEvents.flatMapConcat { react(it) }.onEach { + val newEvents = flowOf(this@computeNewEvents, resultingEvents).flattenConcat().computeNewEvents() + resultingEvents = flowOf(resultingEvents, newEvents).flattenConcat() + }.collect() + + emitAll(resultingEvents) +} + + +/** + * Handle command - Event-sourced aggregate/decider + * @receiver [IDecider] - context receiver + * @receiver [EventRepository] - context receiver + * @receiver command of type C to be handled + */ +context (IDecider, EventRepository) +fun C.handle(): Flow = fetchEvents().computeNewEvents().save() + +/** + * Handle command - Event-sourced aggregate/decider + * @receiver [EventSourcingAggregate] - context receiver + * @receiver command of type C to be handled + * + * Alternative function to `context (IDecider, EventRepository) C.handle()`, which combines multiple contexts ([IDecider], [EventRepository]) into a single meaningful interface/context [EventSourcingAggregate] + */ +context (EventSourcingAggregate) + @FlowPreview +fun C.handleIt(): Flow = fetchEvents().computeNewEvents(this).save() + +/** + * Handle command - Event-sourced orchestrating aggregate/decider + * @receiver [IDecider] - context receiver + * @receiver [ISaga] - context receiver + * @receiver [EventRepository] - context receiver + * @receiver command of type C to be handled + */ +context (IDecider, ISaga, EventRepository) + @FlowPreview +fun C.handle(): Flow = fetchEvents().computeNewEvents().save() + +/** + * Handle command - Event-sourced orchestrating aggregate/decider + * @receiver [EventSourcingOrchestratingAggregate] - context receiver + * @receiver command of type C to be handled + * + * Alternative function to `context (IDecider, ISaga, EventRepository) C.handle()`, which combines multiple contexts ([IDecider], [ISaga], [EventRepository]) into a single meaningful interface/context [EventSourcingOrchestratingAggregate] + */ +context (EventSourcingOrchestratingAggregate) + @FlowPreview +fun C.handleIt(): Flow = fetchEvents().computeNewEvents(this).save() + + +/** + * Handle command(s) - Event-sourced aggregate/decider + * @receiver [IDecider] - context receiver + * @receiver [EventRepository] - context receiver + * @receiver commands of type Flow to be handled + */ +context (IDecider, EventRepository) + @FlowPreview +fun Flow.handle(): Flow = flatMapConcat { it.handle() } + +/** + * Handle command(s) - Event-sourced aggregate/decider + * @receiver [EventSourcingAggregate] - context receiver + * @receiver commands of type Flow to be handled + * + * Alternative function to `context (IDecider, EventRepository) Flow.handle()`, which combines multiple contexts ([IDecider], [EventRepository]) into a single meaningful interface/context [EventSourcingAggregate] + */ +context (EventSourcingAggregate) + @FlowPreview +fun Flow.handleIt(): Flow = flatMapConcat { it.handleIt() } + +/** + * Handle command(s) - Event-sourced orchestrating aggregate/decider + * @receiver [IDecider] - context receiver + * @receiver [ISaga] - context receiver + * @receiver [EventRepository] - context receiver + * @receiver commands of type Flow to be handled + */ +context (IDecider, ISaga, EventRepository) + @FlowPreview +fun Flow.handle(): Flow = flatMapConcat { it.handle() } + +/** + * Handle command(s) - Event-sourced orchestrating aggregate/decider + * @receiver [EventSourcingOrchestratingAggregate] - context receiver + * @receiver commands of type Flow to be handled + * + * Alternative function to `context (IDecider, ISaga, EventRepository) Flow.handle()`, which combines multiple contexts ([IDecider], [ISaga], [EventRepository]) into a single meaningful interface/context [EventSourcingOrchestratingAggregate] + */ +context (EventSourcingOrchestratingAggregate) + @FlowPreview +fun Flow.handleIt(): Flow = flatMapConcat { it.handleIt() } diff --git a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt new file mode 100644 index 00000000..55350e1b --- /dev/null +++ b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt @@ -0,0 +1,53 @@ +package com.fraktalio.fmodel.application + +import com.fraktalio.fmodel.domain.IView +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + + +/** + * Materialized View algorithm + * Computes new State based on the previous State and the Event. + */ +context (IView, E) + internal fun S?.computeNewState(): S = evolve(this ?: initialState, this@E) + + +/** + * Handle event - Materialized View + * @receiver [IView] - context receiver + * @receiver [ViewStateRepository] - context receiver + * @receiver event of type E to be handled + */ +context (IView, ViewStateRepository) + suspend fun E.handle(): S = fetchState().computeNewState().save() + +/** + * Handle event - Materialized View + * @receiver [MaterializedView] - context receiver + * @receiver event of type E to be handled + * + * Alternative function to `context (IView, ViewStateRepository) E.handle()`, which combines multiple contexts ([IView], [ViewStateRepository]) into a single meaningful interface/context [MaterializedView] + */ +context (MaterializedView) + suspend fun E.handleIt(): S = fetchState().computeNewState(this).save() + + +/** + * Handle event(s) - Materialized View + * @receiver [IView] - context receiver + * @receiver [ViewStateRepository] - context receiver + * @receiver events of type Flow to be handled + */ +context (IView, ViewStateRepository) +fun Flow.handle(): Flow = map { it.handle() } + +/** + * Handle event(s) - Materialized View + * @receiver [MaterializedView] - context receiver + * @receiver events of type Flow to be handled + * + * Alternative function to `context (IView, ViewStateRepository) Flow.handle()`, which combines multiple contexts ([IView], [ViewStateRepository]) into a single meaningful interface/context [MaterializedView] + */ +context (MaterializedView) +fun Flow.handleIt(): Flow = map { it.handleIt() } diff --git a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerContextualExtension.kt b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerContextualExtension.kt new file mode 100644 index 00000000..be095732 --- /dev/null +++ b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerContextualExtension.kt @@ -0,0 +1,54 @@ +package com.fraktalio.fmodel.application + +import com.fraktalio.fmodel.domain.ISaga +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapConcat + + +/** + * Materialized View algorithm + * Computes new State based on the previous State and the Event. + */ +context (ISaga, ActionPublisher) + internal fun AR.computeNewActions(): Flow = react(this) + +/** + * Handle event / action result - Saga Manager + * @receiver [ISaga] - context receiver + * @receiver [ActionPublisher] - context receiver + * @receiver event/action result of type AR to be handled + */ +context (ISaga, ActionPublisher) +fun AR.handle(): Flow = computeNewActions().publish() + +/** + * Handle event / action result - Saga Manager + * @receiver [SagaManager] - context receiver + * @receiver event/action result of type AR to be handled + * + * Alternative function to `context (ISaga, ActionPublisher) AR.handle()`, which combines multiple contexts ([ISaga], [ActionPublisher]) into a single meaningful interface/context [SagaManager] + */ +context (SagaManager) +fun AR.handleIt(): Flow = computeNewActions().publish() + +/** + * Handle event / action result - Saga Manager + * @receiver [ISaga] - context receiver + * @receiver [ActionPublisher] - context receiver + * @receiver events/actions result of type Flow to be handled + */ +context (ISaga, ActionPublisher) + @FlowPreview +fun Flow.handle(): Flow = flatMapConcat { it.handle() } + +/** + * Handle event / action result - Saga Manager + * @receiver [SagaManager] - context receiver + * @receiver events/actions result of type Flow to be handled + * + * Alternative function to `context (ISaga, ActionPublisher) Flow.handle()`, which combines multiple contexts ([ISaga], [ActionPublisher]) into a single meaningful interface/context [SagaManager] + */ +context (SagaManager) + @FlowPreview +fun Flow.handleIt(): Flow = flatMapConcat { it.handleIt() } diff --git a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualExtension.kt b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualExtension.kt new file mode 100644 index 00000000..56c8adbd --- /dev/null +++ b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualExtension.kt @@ -0,0 +1,118 @@ +package com.fraktalio.fmodel.application + +import com.fraktalio.fmodel.domain.IDecider +import com.fraktalio.fmodel.domain.ISaga +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.* + + +/** + * State-stored aggregate/decider algorithm + * Computes new State based on the previous State and the Command. + */ +context (IDecider, C) + internal suspend fun S?.computeNewState(): S { + val currentState = this ?: initialState + val events = decide(this@C, currentState) + return events.fold(currentState) { s, e -> evolve(s, e) } +} + +/** + * State-stored orchestrating aggregate/decider algorithm + * Computes new State based on the previous State and the Command. + * Saga might react on Events and send new Commands to the Decider. + */ +context (IDecider, ISaga, C) + @FlowPreview + internal suspend fun S?.computeNewState(): S { + val currentState = this ?: initialState + val events = decide(this@C, currentState) + val newState = events.fold(currentState) { s, e -> evolve(s, e) } + events.flatMapConcat { react(it) }.onEach { newState.computeNewState() }.collect() + return newState +} + +/** + * Handle command - State-stored aggregate/decider + * @receiver [IDecider] - context receiver + * @receiver [StateRepository] - context receiver + * @receiver command of type C to be handled + */ +context (IDecider, StateRepository) + suspend fun C.handle(): S = fetchState().computeNewState().save() + +/** + * Handle command - State-stored aggregate/decider + * @receiver [StateStoredAggregate] - context receiver + * @receiver command of type C to be handled + * + * Alternative function to `context (IDecider, StateRepository) C.handle()`, which combines multiple contexts ([IDecider], [StateRepository]) into a single meaningful interface/context [StateStoredAggregate] + */ +context (StateStoredAggregate) + @FlowPreview + suspend fun C.handleIt(): S = fetchState().computeNewState(this).save() + +/** + * Handle command - State-stored orchestrating aggregate/decider + * @receiver [IDecider] - context receiver + * @receiver [ISaga] - context receiver + * @receiver [StateRepository] - context receiver + * @receiver command of type C to be handled + */ +context (IDecider, ISaga, StateRepository) + @FlowPreview + suspend fun C.handle(): S = fetchState().computeNewState().save() + +/** + * Handle command - State-stored orchestrating aggregate/decider + * @receiver [StateStoredOrchestratingAggregate] - context receiver + * @receiver command of type C to be handled + * + * Alternative function to `context (IDecider, ISaga, StateRepository) C.handle()`, which combines multiple contexts ([IDecider], [ISaga], [StateRepository]) into a single meaningful interface/context [StateStoredOrchestratingAggregate] + */ +context (StateStoredOrchestratingAggregate) + @FlowPreview + suspend fun C.handleIt(): S = fetchState().computeNewState(this).save() + + +/** + * Handle command(s) - State-stored aggregate/decider + * @receiver [IDecider] - context receiver + * @receiver [StateRepository] - context receiver + * @receiver commands of type `Flow` to be handled + */ +context (IDecider, StateRepository) +fun Flow.handle(): Flow = map { it.handle() } + +/** + * Handle command(s) - State-stored aggregate/decider + * @receiver [StateStoredAggregate] - context receiver + * @receiver commands of type `Flow` to be handled + * + * Alternative function to `context (IDecider, StateRepository) Flow.handle()`, which combines multiple contexts ([IDecider], [StateRepository]) into a single meaningful interface/context [StateStoredAggregate] + */ +context (StateStoredAggregate) + @FlowPreview +fun Flow.handleIt(): Flow = map { it.handleIt() } + +/** + * Handle command(s) - State-stored orchestrating aggregate/decider + * @receiver [IDecider] - context receiver + * @receiver [ISaga] - context receiver + * @receiver [StateRepository] - context receiver + * @receiver commands of type `Flow` to be handled + */ +context (IDecider, ISaga, StateRepository) + @FlowPreview + suspend fun Flow.handle(): Flow = map { it.handle() } + +/** + * Handle command(s) - State-stored orchestrating aggregate/decider + * @receiver [StateStoredOrchestratingAggregate] - context receiver + * @receiver commands of type `Flow` to be handled + * + * Alternative function to `context (IDecider, ISaga, StateRepository) Flow.handle()`, which combines multiple contexts ([IDecider], [ISaga], [StateRepository]) into a single meaningful interface/context [StateStoredOrchestratingAggregate] + */ +context (StateStoredOrchestratingAggregate) + @FlowPreview + suspend fun Flow.handleIt(): Flow = map { it.handleIt() } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 01604673..8945cb53 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ coroutines = "1.6.0" dokka = "1.6.10" kotest = "5.1.0" -kotlin = "1.6.10" +kotlin = "1.6.20-RC" arrow = "1.0.1" [libraries] From 9cdabf35b2edd37e2043c7eced305da4497d1eb7 Mon Sep 17 00:00:00 2001 From: Ivan Dugalic Date: Fri, 25 Mar 2022 19:09:23 +0100 Subject: [PATCH 02/13] Prototype of Context Receivers - Kotlin upgraded to 1.6.20-RC2 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8945cb53..dc7527d7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ coroutines = "1.6.0" dokka = "1.6.10" kotest = "5.1.0" -kotlin = "1.6.20-RC" +kotlin = "1.6.20-RC2" arrow = "1.0.1" [libraries] From 84d52c3049631f5ebb68cfc56452bb307770e1cd Mon Sep 17 00:00:00 2001 From: Ivan Dugalic Date: Fri, 25 Mar 2022 19:11:30 +0100 Subject: [PATCH 03/13] Prototype of Context Receivers - Indented --- ...entSourcingAggregateContextualExtension.kt | 20 ++++++------- .../MaterializedViewContextualExtension.kt | 6 ++-- .../SagaManagerContextualExtension.kt | 6 ++-- ...StateStoredAggregateContextualExtension.kt | 30 +++++++++---------- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateContextualExtension.kt b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateContextualExtension.kt index c002b7e0..1bc102d0 100644 --- a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateContextualExtension.kt +++ b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateContextualExtension.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.* * Computes new Events based on the previous Events and the Command. */ context (IDecider, C) - internal fun Flow.computeNewEvents(): Flow = flow { +internal fun Flow.computeNewEvents(): Flow = flow { val currentState = fold(initialState) { s, e -> evolve(s, e) } val resultingEvents = decide(this@C, currentState) emitAll(resultingEvents) @@ -22,8 +22,8 @@ context (IDecider, C) * Saga might react on Events and send new Commands to the Decider. */ context (IDecider, ISaga, C) - @FlowPreview - internal fun Flow.computeNewEvents(): Flow = flow { +@FlowPreview +internal fun Flow.computeNewEvents(): Flow = flow { val currentState = fold(initialState) { s, e -> evolve(s, e) } var resultingEvents = decide(this@C, currentState) @@ -53,7 +53,7 @@ fun C.handle(): Flow = fetchEvents().computeNewEvents().save() * Alternative function to `context (IDecider, EventRepository) C.handle()`, which combines multiple contexts ([IDecider], [EventRepository]) into a single meaningful interface/context [EventSourcingAggregate] */ context (EventSourcingAggregate) - @FlowPreview +@FlowPreview fun C.handleIt(): Flow = fetchEvents().computeNewEvents(this).save() /** @@ -64,7 +64,7 @@ fun C.handleIt(): Flow = fetchEvents().computeNewEvents(this).save( * @receiver command of type C to be handled */ context (IDecider, ISaga, EventRepository) - @FlowPreview +@FlowPreview fun C.handle(): Flow = fetchEvents().computeNewEvents().save() /** @@ -75,7 +75,7 @@ fun C.handle(): Flow = fetchEvents().computeNewEvents().save() * Alternative function to `context (IDecider, ISaga, EventRepository) C.handle()`, which combines multiple contexts ([IDecider], [ISaga], [EventRepository]) into a single meaningful interface/context [EventSourcingOrchestratingAggregate] */ context (EventSourcingOrchestratingAggregate) - @FlowPreview +@FlowPreview fun C.handleIt(): Flow = fetchEvents().computeNewEvents(this).save() @@ -86,7 +86,7 @@ fun C.handleIt(): Flow = fetchEvents().computeNewEvents(this).save( * @receiver commands of type Flow to be handled */ context (IDecider, EventRepository) - @FlowPreview +@FlowPreview fun Flow.handle(): Flow = flatMapConcat { it.handle() } /** @@ -97,7 +97,7 @@ fun Flow.handle(): Flow = flatMapConcat { it.handle() } * Alternative function to `context (IDecider, EventRepository) Flow.handle()`, which combines multiple contexts ([IDecider], [EventRepository]) into a single meaningful interface/context [EventSourcingAggregate] */ context (EventSourcingAggregate) - @FlowPreview +@FlowPreview fun Flow.handleIt(): Flow = flatMapConcat { it.handleIt() } /** @@ -108,7 +108,7 @@ fun Flow.handleIt(): Flow = flatMapConcat { it.handleIt() } * @receiver commands of type Flow to be handled */ context (IDecider, ISaga, EventRepository) - @FlowPreview +@FlowPreview fun Flow.handle(): Flow = flatMapConcat { it.handle() } /** @@ -119,5 +119,5 @@ fun Flow.handle(): Flow = flatMapConcat { it.handle() } * Alternative function to `context (IDecider, ISaga, EventRepository) Flow.handle()`, which combines multiple contexts ([IDecider], [ISaga], [EventRepository]) into a single meaningful interface/context [EventSourcingOrchestratingAggregate] */ context (EventSourcingOrchestratingAggregate) - @FlowPreview +@FlowPreview fun Flow.handleIt(): Flow = flatMapConcat { it.handleIt() } diff --git a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt index 55350e1b..6a8cc94f 100644 --- a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt +++ b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.map * Computes new State based on the previous State and the Event. */ context (IView, E) - internal fun S?.computeNewState(): S = evolve(this ?: initialState, this@E) +internal fun S?.computeNewState(): S = evolve(this ?: initialState, this@E) /** @@ -20,7 +20,7 @@ context (IView, E) * @receiver event of type E to be handled */ context (IView, ViewStateRepository) - suspend fun E.handle(): S = fetchState().computeNewState().save() +suspend fun E.handle(): S = fetchState().computeNewState().save() /** * Handle event - Materialized View @@ -30,7 +30,7 @@ context (IView, ViewStateRepository) * Alternative function to `context (IView, ViewStateRepository) E.handle()`, which combines multiple contexts ([IView], [ViewStateRepository]) into a single meaningful interface/context [MaterializedView] */ context (MaterializedView) - suspend fun E.handleIt(): S = fetchState().computeNewState(this).save() +suspend fun E.handleIt(): S = fetchState().computeNewState(this).save() /** diff --git a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerContextualExtension.kt b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerContextualExtension.kt index be095732..d5648651 100644 --- a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerContextualExtension.kt +++ b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerContextualExtension.kt @@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.flatMapConcat * Computes new State based on the previous State and the Event. */ context (ISaga, ActionPublisher) - internal fun AR.computeNewActions(): Flow = react(this) +internal fun AR.computeNewActions(): Flow = react(this) /** * Handle event / action result - Saga Manager @@ -39,7 +39,7 @@ fun AR.handleIt(): Flow = computeNewActions().publish() * @receiver events/actions result of type Flow to be handled */ context (ISaga, ActionPublisher) - @FlowPreview +@FlowPreview fun Flow.handle(): Flow = flatMapConcat { it.handle() } /** @@ -50,5 +50,5 @@ fun Flow.handle(): Flow = flatMapConcat { it.handle() } * Alternative function to `context (ISaga, ActionPublisher) Flow.handle()`, which combines multiple contexts ([ISaga], [ActionPublisher]) into a single meaningful interface/context [SagaManager] */ context (SagaManager) - @FlowPreview +@FlowPreview fun Flow.handleIt(): Flow = flatMapConcat { it.handleIt() } diff --git a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualExtension.kt b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualExtension.kt index 56c8adbd..a867f201 100644 --- a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualExtension.kt +++ b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualExtension.kt @@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.* * Computes new State based on the previous State and the Command. */ context (IDecider, C) - internal suspend fun S?.computeNewState(): S { +internal suspend fun S?.computeNewState(): S { val currentState = this ?: initialState val events = decide(this@C, currentState) return events.fold(currentState) { s, e -> evolve(s, e) } @@ -23,8 +23,8 @@ context (IDecider, C) * Saga might react on Events and send new Commands to the Decider. */ context (IDecider, ISaga, C) - @FlowPreview - internal suspend fun S?.computeNewState(): S { +@FlowPreview +internal suspend fun S?.computeNewState(): S { val currentState = this ?: initialState val events = decide(this@C, currentState) val newState = events.fold(currentState) { s, e -> evolve(s, e) } @@ -39,7 +39,7 @@ context (IDecider, ISaga, C) * @receiver command of type C to be handled */ context (IDecider, StateRepository) - suspend fun C.handle(): S = fetchState().computeNewState().save() +suspend fun C.handle(): S = fetchState().computeNewState().save() /** * Handle command - State-stored aggregate/decider @@ -49,8 +49,8 @@ context (IDecider, StateRepository) * Alternative function to `context (IDecider, StateRepository) C.handle()`, which combines multiple contexts ([IDecider], [StateRepository]) into a single meaningful interface/context [StateStoredAggregate] */ context (StateStoredAggregate) - @FlowPreview - suspend fun C.handleIt(): S = fetchState().computeNewState(this).save() +@FlowPreview +suspend fun C.handleIt(): S = fetchState().computeNewState(this).save() /** * Handle command - State-stored orchestrating aggregate/decider @@ -60,8 +60,8 @@ context (StateStoredAggregate) * @receiver command of type C to be handled */ context (IDecider, ISaga, StateRepository) - @FlowPreview - suspend fun C.handle(): S = fetchState().computeNewState().save() +@FlowPreview +suspend fun C.handle(): S = fetchState().computeNewState().save() /** * Handle command - State-stored orchestrating aggregate/decider @@ -71,8 +71,8 @@ context (IDecider, ISaga, StateRepository) * Alternative function to `context (IDecider, ISaga, StateRepository) C.handle()`, which combines multiple contexts ([IDecider], [ISaga], [StateRepository]) into a single meaningful interface/context [StateStoredOrchestratingAggregate] */ context (StateStoredOrchestratingAggregate) - @FlowPreview - suspend fun C.handleIt(): S = fetchState().computeNewState(this).save() +@FlowPreview +suspend fun C.handleIt(): S = fetchState().computeNewState(this).save() /** @@ -92,7 +92,7 @@ fun Flow.handle(): Flow = map { it.handle() } * Alternative function to `context (IDecider, StateRepository) Flow.handle()`, which combines multiple contexts ([IDecider], [StateRepository]) into a single meaningful interface/context [StateStoredAggregate] */ context (StateStoredAggregate) - @FlowPreview +@FlowPreview fun Flow.handleIt(): Flow = map { it.handleIt() } /** @@ -103,8 +103,8 @@ fun Flow.handleIt(): Flow = map { it.handleIt() } * @receiver commands of type `Flow` to be handled */ context (IDecider, ISaga, StateRepository) - @FlowPreview - suspend fun Flow.handle(): Flow = map { it.handle() } +@FlowPreview +suspend fun Flow.handle(): Flow = map { it.handle() } /** * Handle command(s) - State-stored orchestrating aggregate/decider @@ -114,5 +114,5 @@ context (IDecider, ISaga, StateRepository) * Alternative function to `context (IDecider, ISaga, StateRepository) Flow.handle()`, which combines multiple contexts ([IDecider], [ISaga], [StateRepository]) into a single meaningful interface/context [StateStoredOrchestratingAggregate] */ context (StateStoredOrchestratingAggregate) - @FlowPreview - suspend fun Flow.handleIt(): Flow = map { it.handleIt() } +@FlowPreview +suspend fun Flow.handleIt(): Flow = map { it.handleIt() } From be1edfe19c2fd4d7d8c80ded0423555b821156bf Mon Sep 17 00:00:00 2001 From: Ivan Dugalic Date: Wed, 30 Mar 2022 19:34:08 +0200 Subject: [PATCH 04/13] Prototype of Context Receivers - Added Actor contextual API plus more tests --- ...entSourcingAggregateContextualExtension.kt | 107 ++++++++++++- .../MaterializedViewContextualExtension.kt | 54 +++++++ .../SagaManagerContextualExtension.kt | 53 +++++++ ...StateStoredAggregateContextualExtension.kt | 100 ++++++++++++ .../EventSourcedAggregateContextualTest.kt | 143 ++++++++++++++++++ .../MaterializedViewContextualTest.kt | 82 ++++++++++ .../StateStoredAggregateContextualTest.kt | 97 ++++++++++++ 7 files changed, 635 insertions(+), 1 deletion(-) create mode 100644 application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateContextualTest.kt create mode 100644 application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualTest.kt create mode 100644 application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualTest.kt diff --git a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateContextualExtension.kt b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateContextualExtension.kt index 1bc102d0..e4449eda 100644 --- a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateContextualExtension.kt +++ b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateContextualExtension.kt @@ -2,8 +2,13 @@ package com.fraktalio.fmodel.application import com.fraktalio.fmodel.domain.IDecider import com.fraktalio.fmodel.domain.ISaga +import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.* +import kotlin.contracts.ExperimentalContracts +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext /** * Event-sourced aggregate/decider algorithm @@ -45,6 +50,7 @@ internal fun Flow.computeNewEvents(): Flow = flow { context (IDecider, EventRepository) fun C.handle(): Flow = fetchEvents().computeNewEvents().save() + /** * Handle command - Event-sourced aggregate/decider * @receiver [EventSourcingAggregate] - context receiver @@ -78,7 +84,6 @@ context (EventSourcingOrchestratingAggregate) @FlowPreview fun C.handleIt(): Flow = fetchEvents().computeNewEvents(this).save() - /** * Handle command(s) - Event-sourced aggregate/decider * @receiver [IDecider] - context receiver @@ -89,6 +94,30 @@ context (IDecider, EventRepository) @FlowPreview fun Flow.handle(): Flow = flatMapConcat { it.handle() } +/** + * Handle command(s) concurrently by the finite number of actors - Event-sourced aggregate/decider + * @receiver [IDecider] - context receiver + * @receiver [EventRepository] - context receiver + * @receiver commands of type Flow to be handled + */ +context (IDecider, EventRepository) +@ExperimentalContracts +fun Flow.handleConcurrently( + numberOfActors: Int = 100, + actorsCapacity: Int = Channel.BUFFERED, + actorsStart: CoroutineStart = CoroutineStart.LAZY, + actorsContext: CoroutineContext = EmptyCoroutineContext, + partitionKey: (C) -> Int +): Flow = + publishConcurrentlyTo( + eventSourcingAggregate(this@IDecider, this@EventRepository), + numberOfActors, + actorsCapacity, + actorsStart, + actorsContext, + partitionKey + ) + /** * Handle command(s) - Event-sourced aggregate/decider * @receiver [EventSourcingAggregate] - context receiver @@ -100,6 +129,32 @@ context (EventSourcingAggregate) @FlowPreview fun Flow.handleIt(): Flow = flatMapConcat { it.handleIt() } +/** + * Handle command(s) concurrently by the finite number of actors - Event-sourced aggregate/decider + * @receiver [EventSourcingAggregate] - context receiver + * @receiver commands of type Flow to be handled + * + * Alternative function to `context (IDecider, EventRepository handleConcurrently(...))`, which combines multiple contexts ([IDecider], [EventRepository]) into a single meaningful interface/context [EventSourcingAggregate] + */ +context (EventSourcingAggregate) +@FlowPreview +@ExperimentalContracts +fun Flow.handleItConcurrently( + numberOfActors: Int = 100, + actorsCapacity: Int = Channel.BUFFERED, + actorsStart: CoroutineStart = CoroutineStart.LAZY, + actorsContext: CoroutineContext = EmptyCoroutineContext, + partitionKey: (C) -> Int +): Flow = + publishConcurrentlyTo( + this@EventSourcingAggregate, + numberOfActors, + actorsCapacity, + actorsStart, + actorsContext, + partitionKey + ) + /** * Handle command(s) - Event-sourced orchestrating aggregate/decider * @receiver [IDecider] - context receiver @@ -111,6 +166,31 @@ context (IDecider, ISaga, EventRepository) @FlowPreview fun Flow.handle(): Flow = flatMapConcat { it.handle() } +/** + * Handle command(s) concurrently by the finite number of actors - Event-sourced aggregate/decider + * @receiver [IDecider] - context receiver + * @receiver [ISaga] - context receiver + * @receiver [EventRepository] - context receiver + * @receiver commands of type Flow to be handled + */ +context (IDecider, ISaga, EventRepository) +@ExperimentalContracts +fun Flow.handleConcurrently( + numberOfActors: Int = 100, + actorsCapacity: Int = Channel.BUFFERED, + actorsStart: CoroutineStart = CoroutineStart.LAZY, + actorsContext: CoroutineContext = EmptyCoroutineContext, + partitionKey: (C) -> Int +): Flow = + publishConcurrentlyTo( + eventSourcingOrchestratingAggregate(this@IDecider, this@EventRepository, this@ISaga), + numberOfActors, + actorsCapacity, + actorsStart, + actorsContext, + partitionKey + ) + /** * Handle command(s) - Event-sourced orchestrating aggregate/decider * @receiver [EventSourcingOrchestratingAggregate] - context receiver @@ -121,3 +201,28 @@ fun Flow.handle(): Flow = flatMapConcat { it.handle() } context (EventSourcingOrchestratingAggregate) @FlowPreview fun Flow.handleIt(): Flow = flatMapConcat { it.handleIt() } + +/** + * Handle command(s) concurrently by the finite number of actors - Event-sourced orchestrating aggregate/decider + * @receiver [EventSourcingOrchestratingAggregate] - context receiver + * @receiver commands of type Flow to be handled + * + * Alternative function to `context (IDecider, ISaga, EventRepository) Flow.handleConcurrently(...)`, which combines multiple contexts ([IDecider], [ISaga], [EventRepository]) into a single meaningful interface/context [EventSourcingOrchestratingAggregate] + */ +context (EventSourcingOrchestratingAggregate) +@ExperimentalContracts +fun Flow.handleItConcurrently( + numberOfActors: Int = 100, + actorsCapacity: Int = Channel.BUFFERED, + actorsStart: CoroutineStart = CoroutineStart.LAZY, + actorsContext: CoroutineContext = EmptyCoroutineContext, + partitionKey: (C) -> Int +): Flow = + publishConcurrentlyTo( + this@EventSourcingOrchestratingAggregate, + numberOfActors, + actorsCapacity, + actorsStart, + actorsContext, + partitionKey + ) diff --git a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt index 6a8cc94f..57107019 100644 --- a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt +++ b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt @@ -1,8 +1,13 @@ package com.fraktalio.fmodel.application import com.fraktalio.fmodel.domain.IView +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import kotlin.contracts.ExperimentalContracts +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext /** @@ -42,6 +47,30 @@ suspend fun E.handleIt(): S = fetchState().computeNewState(this).save() context (IView, ViewStateRepository) fun Flow.handle(): Flow = map { it.handle() } + +/** + * Handle event(s) concurrently by the finite number of actors - Materialized View + * @receiver [IView] - context receiver + * @receiver [ViewStateRepository] - context receiver + * @receiver events of type Flow to be handled + */ +context (IView, ViewStateRepository) +@ExperimentalContracts +fun Flow.handleConcurrently( + numberOfActors: Int = 100, + actorsCapacity: Int = Channel.BUFFERED, + actorsStart: CoroutineStart = CoroutineStart.LAZY, + actorsContext: CoroutineContext = EmptyCoroutineContext, + partitionKey: (E) -> Int +): Flow = + publishConcurrentlyTo( + materializedView(this@IView, this@ViewStateRepository), + numberOfActors, + actorsCapacity, + actorsStart, + actorsContext, + partitionKey + ) /** * Handle event(s) - Materialized View * @receiver [MaterializedView] - context receiver @@ -51,3 +80,28 @@ fun Flow.handle(): Flow = map { it.handle() } */ context (MaterializedView) fun Flow.handleIt(): Flow = map { it.handleIt() } + +/** + * Handle event(s) - Materialized View + * @receiver [MaterializedView] - context receiver + * @receiver events of type Flow to be handled + * + * Alternative function to `context (IView, ViewStateRepository) Flow.handleConcurrently(...)`, which combines multiple contexts ([IView], [ViewStateRepository]) into a single meaningful interface/context [MaterializedView] + */ +context (MaterializedView) +@ExperimentalContracts +fun Flow.handleItConcurrently( + numberOfActors: Int = 100, + actorsCapacity: Int = Channel.BUFFERED, + actorsStart: CoroutineStart = CoroutineStart.LAZY, + actorsContext: CoroutineContext = EmptyCoroutineContext, + partitionKey: (E) -> Int +): Flow = + publishConcurrentlyTo( + this@MaterializedView, + numberOfActors, + actorsCapacity, + actorsStart, + actorsContext, + partitionKey + ) diff --git a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerContextualExtension.kt b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerContextualExtension.kt index d5648651..aff3f1b5 100644 --- a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerContextualExtension.kt +++ b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerContextualExtension.kt @@ -1,9 +1,14 @@ package com.fraktalio.fmodel.application import com.fraktalio.fmodel.domain.ISaga +import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flatMapConcat +import kotlin.contracts.ExperimentalContracts +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext /** @@ -42,6 +47,29 @@ context (ISaga, ActionPublisher) @FlowPreview fun Flow.handle(): Flow = flatMapConcat { it.handle() } +/** + * Handle event / action result concurrently by the finite number of actors - Saga Manager + * @receiver [ISaga] - context receiver + * @receiver [ActionPublisher] - context receiver + * @receiver events/actions result of type Flow to be handled + */ +context (ISaga, ActionPublisher) +@ExperimentalContracts +@FlowPreview +fun Flow.handleConcurrently( + numberOfActors: Int = 100, + actorsCapacity: Int = Channel.BUFFERED, + actorsStart: CoroutineStart = CoroutineStart.LAZY, + actorsContext: CoroutineContext = EmptyCoroutineContext, + partitionKey: (AR) -> Int +): Flow = publishConcurrentlyTo( + sagaManager(this@ISaga, this@ActionPublisher), + numberOfActors, + actorsCapacity, + actorsStart, + actorsContext, + partitionKey +) /** * Handle event / action result - Saga Manager * @receiver [SagaManager] - context receiver @@ -52,3 +80,28 @@ fun Flow.handle(): Flow = flatMapConcat { it.handle() } context (SagaManager) @FlowPreview fun Flow.handleIt(): Flow = flatMapConcat { it.handleIt() } + +/** + * Handle event / action result concurrently by the finite number of actors - Saga Manager + * @receiver [SagaManager] - context receiver + * @receiver events/actions result of type Flow to be handled + * + * Alternative function to `context (ISaga, ActionPublisher) Flow.handleConcurrently(...)`, which combines multiple contexts ([ISaga], [ActionPublisher]) into a single meaningful interface/context [SagaManager] + */ +context (SagaManager) +@ExperimentalContracts +@FlowPreview +fun Flow.handleItConcurrently( + numberOfActors: Int = 100, + actorsCapacity: Int = Channel.BUFFERED, + actorsStart: CoroutineStart = CoroutineStart.LAZY, + actorsContext: CoroutineContext = EmptyCoroutineContext, + partitionKey: (AR) -> Int +): Flow = publishConcurrentlyTo( + this@SagaManager, + numberOfActors, + actorsCapacity, + actorsStart, + actorsContext, + partitionKey +) diff --git a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualExtension.kt b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualExtension.kt index a867f201..2a2e6b62 100644 --- a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualExtension.kt +++ b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualExtension.kt @@ -2,8 +2,13 @@ package com.fraktalio.fmodel.application import com.fraktalio.fmodel.domain.IDecider import com.fraktalio.fmodel.domain.ISaga +import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.* +import kotlin.contracts.ExperimentalContracts +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext /** @@ -84,6 +89,29 @@ suspend fun C.handleIt(): S = fetchState().computeNewState(this).save( context (IDecider, StateRepository) fun Flow.handle(): Flow = map { it.handle() } +/** + * Handle command(s) concurrently by the finite number of actors - State-stored aggregate/decider + * @receiver [IDecider] - context receiver + * @receiver [StateRepository] - context receiver + * @receiver commands of type `Flow` to be handled + */ +context (IDecider, StateRepository) +@ExperimentalContracts +fun Flow.handleConcurrently( + numberOfActors: Int = 100, + actorsCapacity: Int = Channel.BUFFERED, + actorsStart: CoroutineStart = CoroutineStart.LAZY, + actorsContext: CoroutineContext = EmptyCoroutineContext, + partitionKey: (C) -> Int +): Flow = publishConcurrentlyTo( + stateStoredAggregate(this@IDecider, this@StateRepository), + numberOfActors, + actorsCapacity, + actorsStart, + actorsContext, + partitionKey +) + /** * Handle command(s) - State-stored aggregate/decider * @receiver [StateStoredAggregate] - context receiver @@ -95,6 +123,30 @@ context (StateStoredAggregate) @FlowPreview fun Flow.handleIt(): Flow = map { it.handleIt() } +/** + * Handle command(s) concurrently by the finite number of actors - State-stored aggregate/decider + * @receiver [StateStoredAggregate] - context receiver + * @receiver commands of type `Flow` to be handled + * + * Alternative function to `context (IDecider, StateRepository) Flow.handleConcurrently(...)`, which combines multiple contexts ([IDecider], [StateRepository]) into a single meaningful interface/context [StateStoredAggregate] + */ +context (StateStoredAggregate) +@ExperimentalContracts +fun Flow.handleItConcurrently( + numberOfActors: Int = 100, + actorsCapacity: Int = Channel.BUFFERED, + actorsStart: CoroutineStart = CoroutineStart.LAZY, + actorsContext: CoroutineContext = EmptyCoroutineContext, + partitionKey: (C) -> Int +): Flow = publishConcurrentlyTo( + this@StateStoredAggregate, + numberOfActors, + actorsCapacity, + actorsStart, + actorsContext, + partitionKey +) + /** * Handle command(s) - State-stored orchestrating aggregate/decider * @receiver [IDecider] - context receiver @@ -106,6 +158,30 @@ context (IDecider, ISaga, StateRepository) @FlowPreview suspend fun Flow.handle(): Flow = map { it.handle() } +/** + * Handle command(s) concurrently by the finite number of actors - State-stored orchestrating aggregate/decider + * @receiver [IDecider] - context receiver + * @receiver [ISaga] - context receiver + * @receiver [StateRepository] - context receiver + * @receiver commands of type `Flow` to be handled + */ +context (IDecider, ISaga, StateRepository) +@ExperimentalContracts +fun Flow.handleConcurrently( + numberOfActors: Int = 100, + actorsCapacity: Int = Channel.BUFFERED, + actorsStart: CoroutineStart = CoroutineStart.LAZY, + actorsContext: CoroutineContext = EmptyCoroutineContext, + partitionKey: (C) -> Int +): Flow = publishConcurrentlyTo( + stateStoredOrchestratingAggregate(this@IDecider, this@StateRepository, this@ISaga), + numberOfActors, + actorsCapacity, + actorsStart, + actorsContext, + partitionKey +) + /** * Handle command(s) - State-stored orchestrating aggregate/decider * @receiver [StateStoredOrchestratingAggregate] - context receiver @@ -116,3 +192,27 @@ suspend fun Flow.handle(): Flow = map { it.handle() } context (StateStoredOrchestratingAggregate) @FlowPreview suspend fun Flow.handleIt(): Flow = map { it.handleIt() } + +/** + * Handle command(s) concurrently by the finite number of actors - State-stored orchestrating aggregate/decider + * @receiver [StateStoredOrchestratingAggregate] - context receiver + * @receiver commands of type `Flow` to be handled + * + * Alternative function to `context (IDecider, ISaga, StateRepository) Flow.handleConcurrently(...)`, which combines multiple contexts ([IDecider], [ISaga], [StateRepository]) into a single meaningful interface/context [StateStoredOrchestratingAggregate] + */ +context (StateStoredOrchestratingAggregate) +@ExperimentalContracts +fun Flow.handleItConcurrently( + numberOfActors: Int = 100, + actorsCapacity: Int = Channel.BUFFERED, + actorsStart: CoroutineStart = CoroutineStart.LAZY, + actorsContext: CoroutineContext = EmptyCoroutineContext, + partitionKey: (C) -> Int +): Flow = publishConcurrentlyTo( + this@StateStoredOrchestratingAggregate, + numberOfActors, + actorsCapacity, + actorsStart, + actorsContext, + partitionKey +) diff --git a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateContextualTest.kt b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateContextualTest.kt new file mode 100644 index 00000000..b2c9e740 --- /dev/null +++ b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateContextualTest.kt @@ -0,0 +1,143 @@ +package com.fraktalio.fmodel.application + +import com.fraktalio.fmodel.application.examples.numbers.even.command.EvenNumberRepository +import com.fraktalio.fmodel.application.examples.numbers.even.command.evenNumberRepository +import com.fraktalio.fmodel.domain.examples.numbers.api.Description +import com.fraktalio.fmodel.domain.examples.numbers.api.NumberCommand.EvenNumberCommand.AddEvenNumber +import com.fraktalio.fmodel.domain.examples.numbers.api.NumberEvent.EvenNumberEvent.EvenNumberAdded +import com.fraktalio.fmodel.domain.examples.numbers.api.NumberValue +import com.fraktalio.fmodel.domain.examples.numbers.even.command.evenNumberDecider +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.collections.shouldContainAll +import io.kotest.matchers.collections.shouldContainExactly +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.toList +import kotlin.contracts.ExperimentalContracts + +/** + * Event sourced aggregate contextual (context receivers) test + */ +@ExperimentalContracts +@FlowPreview +class EventSourcedAggregateContextualTest : FunSpec({ + val evenDecider = evenNumberDecider() + val evenNumberRepository = evenNumberRepository() as EvenNumberRepository + + // version 1 + test("Event-sourced aggregate contextual - add even number") { + evenNumberRepository.deleteAll() + with(eventSourcingAggregate(evenDecider, evenNumberRepository)) { + flowOf( + AddEvenNumber(Description("desc"), NumberValue(6)), + AddEvenNumber(Description("desc"), NumberValue(4)) + ).handle().toList() shouldContainExactly listOf( + EvenNumberAdded(Description("desc"), NumberValue(6)), + EvenNumberAdded(Description("desc"), NumberValue(4)) + ) + } + + } + // version 2 + test("Event-sourced decider and repository - contextual - add even number") { + evenNumberRepository.deleteAll() + with(evenDecider) { + with(evenNumberRepository) { + flowOf( + AddEvenNumber(Description("desc"), NumberValue(6)), + AddEvenNumber(Description("desc"), NumberValue(4)) + ).handle().toList() shouldContainExactly listOf( + EvenNumberAdded(Description("desc"), NumberValue(6)), + EvenNumberAdded(Description("desc"), NumberValue(4)) + ) + } + } + + } + // version 1 + test("Event-sourced aggregate - concurrent and contextual - add even number") { + evenNumberRepository.deleteAll() + with(eventSourcingAggregate(evenDecider, evenNumberRepository)) { + flowOf( + AddEvenNumber(Description("desc"), NumberValue(6)), + AddEvenNumber(Description("desc"), NumberValue(4)) + ) + .handleConcurrently { it?.description.hashCode() } + .toList() shouldContainExactly listOf( + EvenNumberAdded(Description("desc"), NumberValue(6)), + EvenNumberAdded(Description("desc"), NumberValue(4)) + ) + } + } + // version 2 + test("Event-sourced decider and repository - concurrent and contextual - add even number") { + evenNumberRepository.deleteAll() + with(evenDecider) { + with(evenNumberRepository) { + flowOf( + AddEvenNumber(Description("desc"), NumberValue(6)), + AddEvenNumber(Description("desc"), NumberValue(4)) + ) + .handleConcurrently { it?.description.hashCode() } + .toList() shouldContainExactly listOf( + EvenNumberAdded(Description("desc"), NumberValue(6)), + EvenNumberAdded(Description("desc"), NumberValue(4)) + ) + } + } + } + // version 1 + test("Event-sourced aggregate - concurrent and contextual - add even number - different partition keys") { + evenNumberRepository.deleteAll() + // choosing command `value` hash as a partition key. It is not the same for these two commands. + with(eventSourcingAggregate(evenDecider, evenNumberRepository)) { + flowOf( + AddEvenNumber(Description("desc"), NumberValue(6)), + AddEvenNumber(Description("desc"), NumberValue(4)) + ) + .handleConcurrently { it?.description.hashCode() } + .toList() shouldContainAll listOf( + EvenNumberAdded(Description("desc"), NumberValue(4)), + EvenNumberAdded(Description("desc"), NumberValue(6)) + ) + } + } + // version 2 + test("Event-sourced decider and repository - concurrent and contextual - add even number - different partition keys") { + evenNumberRepository.deleteAll() + // choosing command `value` hash as a partition key. It is not the same for these two commands. + with(evenDecider) { + with(evenNumberRepository) { + flowOf( + AddEvenNumber(Description("desc"), NumberValue(6)), + AddEvenNumber(Description("desc"), NumberValue(4)) + ) + .handleConcurrently { it?.description.hashCode() } + .toList() shouldContainAll listOf( + EvenNumberAdded(Description("desc"), NumberValue(4)), + EvenNumberAdded(Description("desc"), NumberValue(6)) + ) + } + } + + } + + test("Event-sourced aggregate concurrent and contextual - add even number - exception (large number > 1000)") { + shouldThrow { + evenNumberRepository.deleteAll() + with(evenDecider) { + with(evenNumberRepository) { + flowOf( + AddEvenNumber(Description("2000"), NumberValue(2000)) + ) + .handleConcurrently { it?.description.hashCode() }.toList() shouldContainAll listOf( + EvenNumberAdded(Description("2000"), NumberValue(2000)) + ) + } + } + } + } + + +}) diff --git a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualTest.kt b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualTest.kt new file mode 100644 index 00000000..910d644a --- /dev/null +++ b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualTest.kt @@ -0,0 +1,82 @@ +package com.fraktalio.fmodel.application + +import com.fraktalio.fmodel.application.examples.numbers.even.query.EvenNumberViewRepository +import com.fraktalio.fmodel.application.examples.numbers.even.query.evenNumberViewRepository +import com.fraktalio.fmodel.domain.examples.numbers.api.Description +import com.fraktalio.fmodel.domain.examples.numbers.api.EvenNumberState +import com.fraktalio.fmodel.domain.examples.numbers.api.NumberEvent.EvenNumberEvent.EvenNumberAdded +import com.fraktalio.fmodel.domain.examples.numbers.api.NumberValue +import com.fraktalio.fmodel.domain.examples.numbers.even.query.evenNumberView +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.collections.shouldContainExactly +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.toList +import kotlin.contracts.ExperimentalContracts + +/** + * Materialized View Contextual Test + */ +@FlowPreview +@ExperimentalContracts +class MaterializedViewContextualTest : FunSpec({ + val evenView = evenNumberView() + val evenNumberViewRepository = evenNumberViewRepository() as EvenNumberViewRepository + + test("Materialized view contextual - even number added") { + evenNumberViewRepository.deleteAll() + with(evenView) { + with(evenNumberViewRepository) { + flowOf( + EvenNumberAdded(Description("EvenNumberAdded"), NumberValue(2)), + EvenNumberAdded(Description("EvenNumberAdded"), NumberValue(4)) + ).handle().toList() shouldContainExactly listOf( + EvenNumberState(Description("Initial state, EvenNumberAdded"), NumberValue(2)), + EvenNumberState(Description("Initial state, EvenNumberAdded, EvenNumberAdded"), NumberValue(6)) + ) + } + } + } + + test("Materialized view contextual materialized view interface - even number added") { + evenNumberViewRepository.deleteAll() + with(materializedView(evenView, evenNumberViewRepository)) { + flowOf( + EvenNumberAdded(Description("EvenNumberAdded"), NumberValue(2)), + EvenNumberAdded(Description("EvenNumberAdded"), NumberValue(4)) + ).handle().toList() shouldContainExactly listOf( + EvenNumberState(Description("Initial state, EvenNumberAdded"), NumberValue(2)), + EvenNumberState(Description("Initial state, EvenNumberAdded, EvenNumberAdded"), NumberValue(6)) + ) + } + } + + test("Materialized view concurrent and contextual - even number added") { + evenNumberViewRepository.deleteAll() + with(evenView) { + with(evenNumberViewRepository) { + flowOf( + EvenNumberAdded(Description("EvenNumberAdded"), NumberValue(2)), + EvenNumberAdded(Description("EvenNumberAdded"), NumberValue(4)) + ).handleConcurrently { it?.description.hashCode() }.toList() shouldContainExactly listOf( + EvenNumberState(Description("Initial state, EvenNumberAdded"), NumberValue(2)), + EvenNumberState(Description("Initial state, EvenNumberAdded, EvenNumberAdded"), NumberValue(6)) + ) + } + } + } + + test("Materialized view concurrent and contextual materialized view interface - even number added") { + evenNumberViewRepository.deleteAll() + with(materializedView(evenView, evenNumberViewRepository)) { + flowOf( + EvenNumberAdded(Description("EvenNumberAdded"), NumberValue(2)), + EvenNumberAdded(Description("EvenNumberAdded"), NumberValue(4)) + ).handleConcurrently { it?.description.hashCode() }.toList() shouldContainExactly listOf( + EvenNumberState(Description("Initial state, EvenNumberAdded"), NumberValue(2)), + EvenNumberState(Description("Initial state, EvenNumberAdded, EvenNumberAdded"), NumberValue(6)) + ) + } + } + +}) diff --git a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualTest.kt b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualTest.kt new file mode 100644 index 00000000..167f6938 --- /dev/null +++ b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualTest.kt @@ -0,0 +1,97 @@ +package com.fraktalio.fmodel.application + +import com.fraktalio.fmodel.application.examples.numbers.even.command.EvenNumberStateRepository +import com.fraktalio.fmodel.application.examples.numbers.even.command.evenNumberStateRepository +import com.fraktalio.fmodel.domain.examples.numbers.api.Description +import com.fraktalio.fmodel.domain.examples.numbers.api.EvenNumberState +import com.fraktalio.fmodel.domain.examples.numbers.api.NumberCommand.EvenNumberCommand.AddEvenNumber +import com.fraktalio.fmodel.domain.examples.numbers.api.NumberValue +import com.fraktalio.fmodel.domain.examples.numbers.even.command.evenNumberDecider +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.collections.shouldContainExactly +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.toList +import kotlin.contracts.ExperimentalContracts + +/** + * State-stored aggregate contextual test + */ +@ExperimentalContracts +@FlowPreview +class StateStoredAggregateContextualTest : FunSpec({ + val evenDecider = evenNumberDecider() + val evenNumberStateRepository = evenNumberStateRepository() as EvenNumberStateRepository + + test("State-stored aggregate contextual - add even number") { + evenNumberStateRepository.deleteAll() + with(evenDecider) { + with(evenNumberStateRepository) { + flowOf( + AddEvenNumber(Description("desc"), NumberValue(6)), + AddEvenNumber(Description("desc"), NumberValue(4)) + ).handle().toList() shouldContainExactly listOf( + EvenNumberState(Description("desc"), NumberValue(6)), + EvenNumberState(Description("desc"), NumberValue(10)) + ) + } + } + } + + test("State-stored aggregate contextual with aggregate interface - add even number") { + evenNumberStateRepository.deleteAll() + with(stateStoredAggregate(evenDecider, evenNumberStateRepository)) { + flowOf( + AddEvenNumber(Description("desc"), NumberValue(6)), + AddEvenNumber(Description("desc"), NumberValue(4)) + ).handle().toList() shouldContainExactly listOf( + EvenNumberState(Description("desc"), NumberValue(6)), + EvenNumberState(Description("desc"), NumberValue(10)) + ) + } + } + + test("State-stored aggregate concurrent and contextual - add even number") { + evenNumberStateRepository.deleteAll() + with(evenDecider) { + with(evenNumberStateRepository) { + flowOf( + AddEvenNumber(Description("desc"), NumberValue(6)), + AddEvenNumber(Description("desc"), NumberValue(4)) + ).handleConcurrently { it?.description.hashCode() }.toList() shouldContainExactly listOf( + EvenNumberState(Description("desc"), NumberValue(6)), + EvenNumberState(Description("desc"), NumberValue(10)) + ) + } + } + } + + test("State-stored aggregate concurrent and contextual with aggregate interface - add even number") { + evenNumberStateRepository.deleteAll() + with(stateStoredAggregate(evenDecider, evenNumberStateRepository)) { + flowOf( + AddEvenNumber(Description("desc"), NumberValue(6)), + AddEvenNumber(Description("desc"), NumberValue(4)) + ).handleConcurrently { it?.description.hashCode() }.toList() shouldContainExactly listOf( + EvenNumberState(Description("desc"), NumberValue(6)), + EvenNumberState(Description("desc"), NumberValue(10)) + ) + } + } + + test("State-stored aggregate contextual - add even number - exception (large number > 1000)") { + shouldThrow { + + with(evenDecider) { + with(evenNumberStateRepository) { + flowOf( + AddEvenNumber(Description("desc"), NumberValue(6000)) + ).handle().toList() shouldContainExactly listOf( + EvenNumberState(Description("desc"), NumberValue(6000)) + ) + } + } + } + } +}) From dc30ca3397740653d13733b73927e4954e7ec5df Mon Sep 17 00:00:00 2001 From: Ivan Dugalic Date: Tue, 27 Sep 2022 18:55:20 +0200 Subject: [PATCH 05/13] feature-context-receivers: adopting new interfaces --- ...entSourcingAggregateContextualExtension.kt | 248 ++++++++--------- .../MaterializedViewContextualExtension.kt | 151 ++++++---- ...StateStoredAggregateContextualExtension.kt | 257 +++++++++--------- .../EventSourcedAggregateContextualTest.kt | 87 ------ .../MaterializedViewContextualTest.kt | 29 -- .../StateStoredAggregateContextualTest.kt | 28 -- .../application/StateStoredAggregate.kt | 1 - 7 files changed, 326 insertions(+), 475 deletions(-) diff --git a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateContextualExtension.kt b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateContextualExtension.kt index e4449eda..d1f579bd 100644 --- a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateContextualExtension.kt +++ b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateContextualExtension.kt @@ -2,65 +2,60 @@ package com.fraktalio.fmodel.application import com.fraktalio.fmodel.domain.IDecider import com.fraktalio.fmodel.domain.ISaga -import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.* -import kotlin.contracts.ExperimentalContracts -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext -/** - * Event-sourced aggregate/decider algorithm - * Computes new Events based on the previous Events and the Command. - */ -context (IDecider, C) -internal fun Flow.computeNewEvents(): Flow = flow { - val currentState = fold(initialState) { s, e -> evolve(s, e) } - val resultingEvents = decide(this@C, currentState) - emitAll(resultingEvents) -} /** - * Event-sourced orchestrating aggregate/decider algorithm - * Computes new Events based on the previous Events and the Command. - * Saga might react on Events and send new Commands to the Decider. + * Handle command - Event-sourced aggregate/decider + * @receiver [IDecider] - context receiver + * @receiver [EventRepository] - context receiver + * @receiver command of type C to be handled */ -context (IDecider, ISaga, C) -@FlowPreview -internal fun Flow.computeNewEvents(): Flow = flow { - val currentState = fold(initialState) { s, e -> evolve(s, e) } - var resultingEvents = decide(this@C, currentState) - - resultingEvents.flatMapConcat { react(it) }.onEach { - val newEvents = flowOf(this@computeNewEvents, resultingEvents).flattenConcat().computeNewEvents() - resultingEvents = flowOf(resultingEvents, newEvents).flattenConcat() - }.collect() - - emitAll(resultingEvents) -} - +context (IDecider, EventRepository) +fun C.handle(): Flow = + with(object : EventComputation, IDecider by this@IDecider {}) { + this@handle.handleIt() + } /** * Handle command - Event-sourced aggregate/decider - * @receiver [IDecider] - context receiver + * @receiver [EventComputation] - context receiver * @receiver [EventRepository] - context receiver * @receiver command of type C to be handled */ -context (IDecider, EventRepository) -fun C.handle(): Flow = fetchEvents().computeNewEvents().save() +context (EventComputation, EventRepository) +fun C.handleIt(): Flow = fetchEvents().computeNewEvents(this).save() +/** + * Handle command optimistically - Event-sourced locking aggregate/decider + * @receiver [IDecider] - context receiver + * @receiver [EventLockingRepository] - context receiver + * @receiver command of type C to be handled + */ +context (IDecider, EventLockingRepository) +@FlowPreview +fun C.handleOptimistically(): Flow> = + with(object : EventComputation, IDecider by this@IDecider {}) { + this@handleOptimistically.handleItOptimistically() + } /** - * Handle command - Event-sourced aggregate/decider - * @receiver [EventSourcingAggregate] - context receiver + * Handle command optimistically - Event-sourced locking aggregate/decider + * @receiver [EventComputation] - context receiver + * @receiver [EventLockingRepository] - context receiver * @receiver command of type C to be handled - * - * Alternative function to `context (IDecider, EventRepository) C.handle()`, which combines multiple contexts ([IDecider], [EventRepository]) into a single meaningful interface/context [EventSourcingAggregate] */ -context (EventSourcingAggregate) +context (EventComputation, EventLockingRepository) @FlowPreview -fun C.handleIt(): Flow = fetchEvents().computeNewEvents(this).save() +fun C.handleItOptimistically(): Flow> = flow { + val events = this@handleItOptimistically.fetchEvents() + emitAll( + events.map { it.first } + .computeNewEvents(this@handleItOptimistically) + .save(events.lastOrNull()) + ) +} /** * Handle command - Event-sourced orchestrating aggregate/decider @@ -71,18 +66,50 @@ fun C.handleIt(): Flow = fetchEvents().computeNewEvents(this).save( */ context (IDecider, ISaga, EventRepository) @FlowPreview -fun C.handle(): Flow = fetchEvents().computeNewEvents().save() +fun C.handle(): Flow = + with(object : EventOrchestratingComputation, IDecider by this@IDecider, + ISaga by this@ISaga {}) { + this@handle.handleIt() + } /** * Handle command - Event-sourced orchestrating aggregate/decider - * @receiver [EventSourcingOrchestratingAggregate] - context receiver + * @receiver [EventOrchestratingComputation] - context receiver + * @receiver [EventRepository] - context receiver * @receiver command of type C to be handled - * - * Alternative function to `context (IDecider, ISaga, EventRepository) C.handle()`, which combines multiple contexts ([IDecider], [ISaga], [EventRepository]) into a single meaningful interface/context [EventSourcingOrchestratingAggregate] */ -context (EventSourcingOrchestratingAggregate) +context (EventOrchestratingComputation, EventRepository) @FlowPreview -fun C.handleIt(): Flow = fetchEvents().computeNewEvents(this).save() +fun C.handleIt(): Flow = fetchEvents().computeNewEventsByOrchestrating(this) { it.fetchEvents() }.save() + +/** + * Handle command optimistically - Event-sourced orchestrating locking aggregate/decider + * @receiver [IDecider] - context receiver + * @receiver [ISaga] - context receiver + * @receiver [EventLockingRepository] - context receiver + * @receiver command of type C to be handled + */ +context (IDecider, ISaga, EventLockingRepository) +@FlowPreview +fun C.handleOptimistically(): Flow> = + with(object : EventOrchestratingComputation, IDecider by this@IDecider, + ISaga by this@ISaga {}) { + this@handleOptimistically.handleItOptimistically() + } + +/** + * Handle command optimistically - Event-sourced orchestrating locking aggregate/decider + * @receiver [EventOrchestratingComputation] - context receiver + * @receiver [EventLockingRepository] - context receiver + * @receiver command of type C to be handled + */ +context (EventOrchestratingComputation, EventLockingRepository) +@FlowPreview +fun C.handleItOptimistically(): Flow> = + this + .fetchEvents().map { it.first } + .computeNewEventsByOrchestrating(this) { it.fetchEvents().map { pair -> pair.first } } + .save(latestVersionProvider) /** * Handle command(s) - Event-sourced aggregate/decider @@ -95,65 +122,34 @@ context (IDecider, EventRepository) fun Flow.handle(): Flow = flatMapConcat { it.handle() } /** - * Handle command(s) concurrently by the finite number of actors - Event-sourced aggregate/decider - * @receiver [IDecider] - context receiver + * Handle command(s) - Event-sourced aggregate/decider + * @receiver [EventComputation] - context receiver * @receiver [EventRepository] - context receiver * @receiver commands of type Flow to be handled */ -context (IDecider, EventRepository) -@ExperimentalContracts -fun Flow.handleConcurrently( - numberOfActors: Int = 100, - actorsCapacity: Int = Channel.BUFFERED, - actorsStart: CoroutineStart = CoroutineStart.LAZY, - actorsContext: CoroutineContext = EmptyCoroutineContext, - partitionKey: (C) -> Int -): Flow = - publishConcurrentlyTo( - eventSourcingAggregate(this@IDecider, this@EventRepository), - numberOfActors, - actorsCapacity, - actorsStart, - actorsContext, - partitionKey - ) +context (EventComputation, EventRepository) +@FlowPreview +fun Flow.handleIt(): Flow = flatMapConcat { it.handleIt() } /** - * Handle command(s) - Event-sourced aggregate/decider - * @receiver [EventSourcingAggregate] - context receiver + * Handle command(s) optimistically - Event-sourced locking aggregate/decider + * @receiver [IDecider] - context receiver + * @receiver [EventLockingRepository] - context receiver * @receiver commands of type Flow to be handled - * - * Alternative function to `context (IDecider, EventRepository) Flow.handle()`, which combines multiple contexts ([IDecider], [EventRepository]) into a single meaningful interface/context [EventSourcingAggregate] */ -context (EventSourcingAggregate) +context (IDecider, EventLockingRepository) @FlowPreview -fun Flow.handleIt(): Flow = flatMapConcat { it.handleIt() } +fun Flow.handleOptimistically(): Flow> = flatMapConcat { it.handleOptimistically() } /** - * Handle command(s) concurrently by the finite number of actors - Event-sourced aggregate/decider - * @receiver [EventSourcingAggregate] - context receiver + * Handle command(s) optimistically - Event-sourced locking aggregate/decider + * @receiver [EventComputation] - context receiver + * @receiver [EventLockingRepository] - context receiver * @receiver commands of type Flow to be handled - * - * Alternative function to `context (IDecider, EventRepository handleConcurrently(...))`, which combines multiple contexts ([IDecider], [EventRepository]) into a single meaningful interface/context [EventSourcingAggregate] */ -context (EventSourcingAggregate) +context (EventComputation, EventLockingRepository) @FlowPreview -@ExperimentalContracts -fun Flow.handleItConcurrently( - numberOfActors: Int = 100, - actorsCapacity: Int = Channel.BUFFERED, - actorsStart: CoroutineStart = CoroutineStart.LAZY, - actorsContext: CoroutineContext = EmptyCoroutineContext, - partitionKey: (C) -> Int -): Flow = - publishConcurrentlyTo( - this@EventSourcingAggregate, - numberOfActors, - actorsCapacity, - actorsStart, - actorsContext, - partitionKey - ) +fun Flow.handleItOptimistically(): Flow> = flatMapConcat { it.handleItOptimistically() } /** * Handle command(s) - Event-sourced orchestrating aggregate/decider @@ -167,62 +163,32 @@ context (IDecider, ISaga, EventRepository) fun Flow.handle(): Flow = flatMapConcat { it.handle() } /** - * Handle command(s) concurrently by the finite number of actors - Event-sourced aggregate/decider - * @receiver [IDecider] - context receiver - * @receiver [ISaga] - context receiver + * Handle command(s) - Event-sourced orchestrating aggregate/decider + * @receiver [EventOrchestratingComputation] - context receiver * @receiver [EventRepository] - context receiver * @receiver commands of type Flow to be handled */ -context (IDecider, ISaga, EventRepository) -@ExperimentalContracts -fun Flow.handleConcurrently( - numberOfActors: Int = 100, - actorsCapacity: Int = Channel.BUFFERED, - actorsStart: CoroutineStart = CoroutineStart.LAZY, - actorsContext: CoroutineContext = EmptyCoroutineContext, - partitionKey: (C) -> Int -): Flow = - publishConcurrentlyTo( - eventSourcingOrchestratingAggregate(this@IDecider, this@EventRepository, this@ISaga), - numberOfActors, - actorsCapacity, - actorsStart, - actorsContext, - partitionKey - ) +context (EventOrchestratingComputation, EventRepository) +@FlowPreview +fun Flow.handleIt(): Flow = flatMapConcat { it.handleIt() } /** - * Handle command(s) - Event-sourced orchestrating aggregate/decider - * @receiver [EventSourcingOrchestratingAggregate] - context receiver + * Handle command(s) optimistically - Event-sourced orchestrating locking aggregate/decider + * @receiver [IDecider] - context receiver + * @receiver [ISaga] - context receiver + * @receiver [EventLockingRepository] - context receiver * @receiver commands of type Flow to be handled - * - * Alternative function to `context (IDecider, ISaga, EventRepository) Flow.handle()`, which combines multiple contexts ([IDecider], [ISaga], [EventRepository]) into a single meaningful interface/context [EventSourcingOrchestratingAggregate] */ -context (EventSourcingOrchestratingAggregate) +context (IDecider, ISaga, EventLockingRepository) @FlowPreview -fun Flow.handleIt(): Flow = flatMapConcat { it.handleIt() } +fun Flow.handleOptimistically(): Flow> = flatMapConcat { it.handleOptimistically() } /** - * Handle command(s) concurrently by the finite number of actors - Event-sourced orchestrating aggregate/decider - * @receiver [EventSourcingOrchestratingAggregate] - context receiver + * Handle command(s) optimistically - Event-sourced orchestrating locking aggregate/decider + * @receiver [EventOrchestratingComputation] - context receiver + * @receiver [EventLockingRepository] - context receiver * @receiver commands of type Flow to be handled - * - * Alternative function to `context (IDecider, ISaga, EventRepository) Flow.handleConcurrently(...)`, which combines multiple contexts ([IDecider], [ISaga], [EventRepository]) into a single meaningful interface/context [EventSourcingOrchestratingAggregate] - */ -context (EventSourcingOrchestratingAggregate) -@ExperimentalContracts -fun Flow.handleItConcurrently( - numberOfActors: Int = 100, - actorsCapacity: Int = Channel.BUFFERED, - actorsStart: CoroutineStart = CoroutineStart.LAZY, - actorsContext: CoroutineContext = EmptyCoroutineContext, - partitionKey: (C) -> Int -): Flow = - publishConcurrentlyTo( - this@EventSourcingOrchestratingAggregate, - numberOfActors, - actorsCapacity, - actorsStart, - actorsContext, - partitionKey - ) + */ +context (EventOrchestratingComputation, EventLockingRepository) +@FlowPreview +fun Flow.handleItOptimistically(): Flow> = flatMapConcat { it.handleItOptimistically() } diff --git a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt index 57107019..044d449a 100644 --- a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt +++ b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt @@ -1,42 +1,89 @@ package com.fraktalio.fmodel.application import com.fraktalio.fmodel.domain.IView -import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import kotlin.contracts.ExperimentalContracts -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext /** - * Materialized View algorithm - * Computes new State based on the previous State and the Event. + * Handle event - Materialized View + * @receiver [IView] - context receiver + * @receiver [ViewStateRepository] - context receiver + * @receiver event of type E to be handled */ -context (IView, E) -internal fun S?.computeNewState(): S = evolve(this ?: initialState, this@E) +context (IView, ViewStateRepository) +suspend fun E.handle(): S = + with(object : ViewStateComputation, IView by this@IView {}) { + this@handle.handleIt() + } +/** + * Handle event - Materialized View + * @receiver [IView] - context receiver + * @receiver [ViewStateLockingRepository] - context receiver + * @receiver event of type E to be handled + */ +context (IView, ViewStateLockingRepository) +suspend fun E.handleOptimistically(): Pair = + with(object : ViewStateComputation, IView by this@IView {}) { + this@handleOptimistically.handleItOptimistically() + } /** * Handle event - Materialized View * @receiver [IView] - context receiver + * @receiver [ViewStateLockingDeduplicationRepository] - context receiver + * @receiver event of type E to be handled + */ +context (IView, ViewStateLockingDeduplicationRepository) +suspend fun E.handleOptimisticallyWithDeduplication(eventAndVersion: Pair): Pair = + with(object : ViewStateComputation, IView by this@IView {}) { + this@handleOptimisticallyWithDeduplication.handleItOptimisticallyWithDeduplication(eventAndVersion) + } +/** + * Handle event - Materialized View + * @receiver [ViewStateComputation] - context receiver * @receiver [ViewStateRepository] - context receiver * @receiver event of type E to be handled + * + * Alternative function to `context (IView, ViewStateRepository) E.handle()` */ -context (IView, ViewStateRepository) -suspend fun E.handle(): S = fetchState().computeNewState().save() +context (ViewStateComputation, ViewStateRepository) +suspend fun E.handleIt(): S = fetchState().computeNewState(this).save() + /** * Handle event - Materialized View - * @receiver [MaterializedView] - context receiver + * @receiver [ViewStateComputation] - context receiver + * @receiver [ViewStateLockingRepository] - context receiver * @receiver event of type E to be handled * - * Alternative function to `context (IView, ViewStateRepository) E.handle()`, which combines multiple contexts ([IView], [ViewStateRepository]) into a single meaningful interface/context [MaterializedView] + * Alternative function to `context (IView, ViewStateLockingRepository) E.handleOptimistically()` */ -context (MaterializedView) -suspend fun E.handleIt(): S = fetchState().computeNewState(this).save() +context (ViewStateComputation, ViewStateLockingRepository) +suspend fun E.handleItOptimistically(): Pair { + val (state, version) = this@handleItOptimistically.fetchState() + return state + .computeNewState(this@handleItOptimistically) + .save(version) +} +/** + * Handle event - Materialized View + * @receiver [ViewStateComputation] - context receiver + * @receiver [ViewStateLockingDeduplicationRepository] - context receiver + * @receiver event of type E to be handled + * + * Alternative function to `context (IView, ViewStateLockingDeduplicationRepository) E.handleOptimisticallyWithDeduplication()` + */ +context (ViewStateComputation, ViewStateLockingDeduplicationRepository) +suspend fun E.handleItOptimisticallyWithDeduplication(eventAndVersion: Pair): Pair { + val (event, eventVersion) = eventAndVersion + val (state, currentStateVersion) = event.fetchState() + return state + .computeNewState(event) + .save(eventVersion, currentStateVersion) +} /** * Handle event(s) - Materialized View @@ -47,61 +94,53 @@ suspend fun E.handleIt(): S = fetchState().computeNewState(this).save() context (IView, ViewStateRepository) fun Flow.handle(): Flow = map { it.handle() } +/** + * Handle event(s) - Materialized View + * @receiver [IView] - context receiver + * @receiver [ViewStateLockingRepository] - context receiver + * @receiver events of type Flow to be handled + */ +context (IView, ViewStateLockingRepository) +fun Flow.handleOptimistically(): Flow> = map { it.handleOptimistically() } /** - * Handle event(s) concurrently by the finite number of actors - Materialized View + * Handle event(s) - Materialized View * @receiver [IView] - context receiver - * @receiver [ViewStateRepository] - context receiver + * @receiver [ViewStateLockingDeduplicationRepository] - context receiver * @receiver events of type Flow to be handled */ -context (IView, ViewStateRepository) -@ExperimentalContracts -fun Flow.handleConcurrently( - numberOfActors: Int = 100, - actorsCapacity: Int = Channel.BUFFERED, - actorsStart: CoroutineStart = CoroutineStart.LAZY, - actorsContext: CoroutineContext = EmptyCoroutineContext, - partitionKey: (E) -> Int -): Flow = - publishConcurrentlyTo( - materializedView(this@IView, this@ViewStateRepository), - numberOfActors, - actorsCapacity, - actorsStart, - actorsContext, - partitionKey - ) +context (IView, ViewStateLockingDeduplicationRepository) +fun Flow.handleOptimisticallyWithDeduplication(eventAndVersion: Pair): Flow> = map { it.handleOptimisticallyWithDeduplication(eventAndVersion) } + /** * Handle event(s) - Materialized View - * @receiver [MaterializedView] - context receiver + * @receiver [ViewStateComputation] - context receiver + * @receiver [ViewStateRepository] - context receiver * @receiver events of type Flow to be handled * - * Alternative function to `context (IView, ViewStateRepository) Flow.handle()`, which combines multiple contexts ([IView], [ViewStateRepository]) into a single meaningful interface/context [MaterializedView] + * Alternative function to `context (IView, ViewStateRepository) Flow.handle()` */ -context (MaterializedView) +context (ViewStateComputation, ViewStateRepository) fun Flow.handleIt(): Flow = map { it.handleIt() } /** * Handle event(s) - Materialized View - * @receiver [MaterializedView] - context receiver + * @receiver [ViewStateComputation] - context receiver + * @receiver [ViewStateLockingRepository] - context receiver + * @receiver events of type Flow to be handled + * + * Alternative function to `context (IView, ViewStateLockingRepository) Flow.handleOptimistically()` + */ +context (ViewStateComputation, ViewStateLockingRepository) +fun Flow.handleItOptimistically(): Flow> = map { it.handleItOptimistically() } + +/** + * Handle event(s) - Materialized View + * @receiver [ViewStateComputation] - context receiver + * @receiver [ViewStateLockingDeduplicationRepository] - context receiver * @receiver events of type Flow to be handled * - * Alternative function to `context (IView, ViewStateRepository) Flow.handleConcurrently(...)`, which combines multiple contexts ([IView], [ViewStateRepository]) into a single meaningful interface/context [MaterializedView] + * Alternative function to `context (IView, ViewStateLockingDeduplicationRepository) Flow.handleOptimisticallyWithDeduplication(eventAndVersion)` */ -context (MaterializedView) -@ExperimentalContracts -fun Flow.handleItConcurrently( - numberOfActors: Int = 100, - actorsCapacity: Int = Channel.BUFFERED, - actorsStart: CoroutineStart = CoroutineStart.LAZY, - actorsContext: CoroutineContext = EmptyCoroutineContext, - partitionKey: (E) -> Int -): Flow = - publishConcurrentlyTo( - this@MaterializedView, - numberOfActors, - actorsCapacity, - actorsStart, - actorsContext, - partitionKey - ) +context (ViewStateComputation, ViewStateLockingDeduplicationRepository) +fun Flow.handleItOptimisticallyWithDeduplication(eventAndVersion: Pair): Flow> = map { it.handleItOptimisticallyWithDeduplication(eventAndVersion) } diff --git a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualExtension.kt b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualExtension.kt index 2a2e6b62..cbb7989e 100644 --- a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualExtension.kt +++ b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualExtension.kt @@ -2,82 +2,121 @@ package com.fraktalio.fmodel.application import com.fraktalio.fmodel.domain.IDecider import com.fraktalio.fmodel.domain.ISaga -import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.* -import kotlin.contracts.ExperimentalContracts -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +/** + * Handle command - State-stored aggregate/decider + * @receiver [IDecider] - context receiver + * @receiver [StateRepository] - context receiver + * @receiver command of type C to be handled + */ +context (IDecider, StateRepository) +suspend fun C.handle(): S = + with(object : StateComputation, IDecider by this@IDecider {}) { + this@handle.handleIt() + } /** - * State-stored aggregate/decider algorithm - * Computes new State based on the previous State and the Command. + * Handle command - State-stored aggregate/decider + * @receiver [IDecider] - context receiver + * @receiver [StateLockingRepository] - context receiver + * @receiver command of type C to be handled */ -context (IDecider, C) -internal suspend fun S?.computeNewState(): S { - val currentState = this ?: initialState - val events = decide(this@C, currentState) - return events.fold(currentState) { s, e -> evolve(s, e) } -} +context (IDecider, StateLockingRepository) +suspend fun C.handleOptimistically(): Pair = + with(object : StateComputation, IDecider by this@IDecider {}) { + this@handleOptimistically.handleItOptimistically() + } /** - * State-stored orchestrating aggregate/decider algorithm - * Computes new State based on the previous State and the Command. - * Saga might react on Events and send new Commands to the Decider. + * Handle command - State-stored orchestrating aggregate/decider + * @receiver [IDecider] - context receiver + * @receiver [ISaga] - context receiver + * @receiver [StateRepository] - context receiver + * @receiver command of type C to be handled */ -context (IDecider, ISaga, C) +context (IDecider, ISaga, StateRepository) @FlowPreview -internal suspend fun S?.computeNewState(): S { - val currentState = this ?: initialState - val events = decide(this@C, currentState) - val newState = events.fold(currentState) { s, e -> evolve(s, e) } - events.flatMapConcat { react(it) }.onEach { newState.computeNewState() }.collect() - return newState -} +suspend fun C.handle(): S = + with(object : StateOrchestratingComputation, + IDecider by this@IDecider, + ISaga by this@ISaga {}) { + this@handle.handleIt() + } /** - * Handle command - State-stored aggregate/decider + * Handle command - State-stored orchestrating aggregate/decider * @receiver [IDecider] - context receiver - * @receiver [StateRepository] - context receiver + * @receiver [ISaga] - context receiver + * @receiver [StateLockingRepository] - context receiver * @receiver command of type C to be handled */ -context (IDecider, StateRepository) -suspend fun C.handle(): S = fetchState().computeNewState().save() +context (IDecider, ISaga, StateLockingRepository) +@FlowPreview +suspend fun C.handleOptimistically(): Pair = + with(object : StateOrchestratingComputation, + IDecider by this@IDecider, + ISaga by this@ISaga {}) { + this@handleOptimistically.handleItOptimistically() + } /** * Handle command - State-stored aggregate/decider - * @receiver [StateStoredAggregate] - context receiver + * @receiver [StateComputation] - context receiver + * @receiver [StateRepository] - context receiver * @receiver command of type C to be handled * - * Alternative function to `context (IDecider, StateRepository) C.handle()`, which combines multiple contexts ([IDecider], [StateRepository]) into a single meaningful interface/context [StateStoredAggregate] + * Alternative function to `context (IDecider, StateRepository) C.handle()` */ -context (StateStoredAggregate) -@FlowPreview +context (StateComputation, StateRepository) suspend fun C.handleIt(): S = fetchState().computeNewState(this).save() +/** + * Handle command - State-stored aggregate/decider + * @receiver [StateComputation] - context receiver + * @receiver [StateLockingRepository] - context receiver + * @receiver command of type C to be handled + * + * Alternative function to `context (IDecider, StateLockingRepository) C.handleOptimistically()` + */ +context (StateComputation, StateLockingRepository) +suspend fun C.handleItOptimistically(): Pair { + val (state, version) = this@handleItOptimistically.fetchState() + return state + .computeNewState(this@handleItOptimistically) + .save(version) +} + /** * Handle command - State-stored orchestrating aggregate/decider - * @receiver [IDecider] - context receiver - * @receiver [ISaga] - context receiver + * @receiver [StateOrchestratingComputation] - context receiver * @receiver [StateRepository] - context receiver * @receiver command of type C to be handled + * + * Alternative function to `context (IDecider, ISaga, StateRepository) C.handle()` */ -context (IDecider, ISaga, StateRepository) +context (StateOrchestratingComputation, StateRepository) @FlowPreview -suspend fun C.handle(): S = fetchState().computeNewState().save() +suspend fun C.handleIt(): S = fetchState().computeNewState(this).save() /** * Handle command - State-stored orchestrating aggregate/decider - * @receiver [StateStoredOrchestratingAggregate] - context receiver + * @receiver [StateOrchestratingComputation] - context receiver + * @receiver [StateLockingRepository] - context receiver * @receiver command of type C to be handled * - * Alternative function to `context (IDecider, ISaga, StateRepository) C.handle()`, which combines multiple contexts ([IDecider], [ISaga], [StateRepository]) into a single meaningful interface/context [StateStoredOrchestratingAggregate] + * Alternative function to `context (IDecider, ISaga, StateLockingRepository) C.handleOptimistically()` */ -context (StateStoredOrchestratingAggregate) +context (StateOrchestratingComputation, StateLockingRepository) @FlowPreview -suspend fun C.handleIt(): S = fetchState().computeNewState(this).save() +suspend fun C.handleItOptimistically(): Pair { + val (state, version) = this@handleItOptimistically.fetchState() + return state + .computeNewState(this@handleItOptimistically) + .save(version) +} /** @@ -90,62 +129,14 @@ context (IDecider, StateRepository) fun Flow.handle(): Flow = map { it.handle() } /** - * Handle command(s) concurrently by the finite number of actors - State-stored aggregate/decider + * Handle command(s) - State-stored aggregate/decider * @receiver [IDecider] - context receiver - * @receiver [StateRepository] - context receiver + * @receiver [StateLockingRepository] - context receiver * @receiver commands of type `Flow` to be handled */ -context (IDecider, StateRepository) -@ExperimentalContracts -fun Flow.handleConcurrently( - numberOfActors: Int = 100, - actorsCapacity: Int = Channel.BUFFERED, - actorsStart: CoroutineStart = CoroutineStart.LAZY, - actorsContext: CoroutineContext = EmptyCoroutineContext, - partitionKey: (C) -> Int -): Flow = publishConcurrentlyTo( - stateStoredAggregate(this@IDecider, this@StateRepository), - numberOfActors, - actorsCapacity, - actorsStart, - actorsContext, - partitionKey -) +context (IDecider, StateLockingRepository) +fun Flow.handleOptimistically(): Flow> = map { it.handleOptimistically() } -/** - * Handle command(s) - State-stored aggregate/decider - * @receiver [StateStoredAggregate] - context receiver - * @receiver commands of type `Flow` to be handled - * - * Alternative function to `context (IDecider, StateRepository) Flow.handle()`, which combines multiple contexts ([IDecider], [StateRepository]) into a single meaningful interface/context [StateStoredAggregate] - */ -context (StateStoredAggregate) -@FlowPreview -fun Flow.handleIt(): Flow = map { it.handleIt() } - -/** - * Handle command(s) concurrently by the finite number of actors - State-stored aggregate/decider - * @receiver [StateStoredAggregate] - context receiver - * @receiver commands of type `Flow` to be handled - * - * Alternative function to `context (IDecider, StateRepository) Flow.handleConcurrently(...)`, which combines multiple contexts ([IDecider], [StateRepository]) into a single meaningful interface/context [StateStoredAggregate] - */ -context (StateStoredAggregate) -@ExperimentalContracts -fun Flow.handleItConcurrently( - numberOfActors: Int = 100, - actorsCapacity: Int = Channel.BUFFERED, - actorsStart: CoroutineStart = CoroutineStart.LAZY, - actorsContext: CoroutineContext = EmptyCoroutineContext, - partitionKey: (C) -> Int -): Flow = publishConcurrentlyTo( - this@StateStoredAggregate, - numberOfActors, - actorsCapacity, - actorsStart, - actorsContext, - partitionKey -) /** * Handle command(s) - State-stored orchestrating aggregate/decider @@ -159,60 +150,60 @@ context (IDecider, ISaga, StateRepository) suspend fun Flow.handle(): Flow = map { it.handle() } /** - * Handle command(s) concurrently by the finite number of actors - State-stored orchestrating aggregate/decider + * Handle command(s) - State-stored orchestrating aggregate/decider * @receiver [IDecider] - context receiver * @receiver [ISaga] - context receiver + * @receiver [StateLockingRepository] - context receiver + * @receiver commands of type `Flow` to be handled + */ +context (IDecider, ISaga, StateLockingRepository) +@FlowPreview +suspend fun Flow.handleOptimistically(): Flow> = map { it.handleOptimistically() } + +/** + * Handle command(s) - State-stored aggregate/decider + * @receiver [StateComputation] - context receiver * @receiver [StateRepository] - context receiver * @receiver commands of type `Flow` to be handled + * + * Alternative function to `context (IDecider, StateRepository) Flow.handle()` */ -context (IDecider, ISaga, StateRepository) -@ExperimentalContracts -fun Flow.handleConcurrently( - numberOfActors: Int = 100, - actorsCapacity: Int = Channel.BUFFERED, - actorsStart: CoroutineStart = CoroutineStart.LAZY, - actorsContext: CoroutineContext = EmptyCoroutineContext, - partitionKey: (C) -> Int -): Flow = publishConcurrentlyTo( - stateStoredOrchestratingAggregate(this@IDecider, this@StateRepository, this@ISaga), - numberOfActors, - actorsCapacity, - actorsStart, - actorsContext, - partitionKey -) +context (StateComputation, StateRepository) +fun Flow.handleIt(): Flow = map { it.handleIt() } + +/** + * Handle command(s) - State-stored aggregate/decider + * @receiver [StateComputation] - context receiver + * @receiver [StateLockingRepository] - context receiver + * @receiver commands of type `Flow` to be handled + * + * Alternative function to `context (IDecider, StateLockingRepository) Flow.handleOptimistically()` + */ +context (StateComputation, StateLockingRepository) +fun Flow.handleItOptimistically(): Flow> = map { it.handleItOptimistically() } + /** * Handle command(s) - State-stored orchestrating aggregate/decider - * @receiver [StateStoredOrchestratingAggregate] - context receiver + * @receiver [StateOrchestratingComputation] - context receiver + * @receiver [StateRepository] - context receiver * @receiver commands of type `Flow` to be handled * - * Alternative function to `context (IDecider, ISaga, StateRepository) Flow.handle()`, which combines multiple contexts ([IDecider], [ISaga], [StateRepository]) into a single meaningful interface/context [StateStoredOrchestratingAggregate] + * Alternative function to `context (IDecider, ISaga, StateRepository) Flow.handle()` */ -context (StateStoredOrchestratingAggregate) +context (StateOrchestratingComputation, StateRepository) @FlowPreview suspend fun Flow.handleIt(): Flow = map { it.handleIt() } /** - * Handle command(s) concurrently by the finite number of actors - State-stored orchestrating aggregate/decider - * @receiver [StateStoredOrchestratingAggregate] - context receiver + * Handle command(s) - State-stored orchestrating aggregate/decider + * @receiver [StateOrchestratingComputation] - context receiver + * @receiver [StateLockingRepository] - context receiver * @receiver commands of type `Flow` to be handled * - * Alternative function to `context (IDecider, ISaga, StateRepository) Flow.handleConcurrently(...)`, which combines multiple contexts ([IDecider], [ISaga], [StateRepository]) into a single meaningful interface/context [StateStoredOrchestratingAggregate] - */ -context (StateStoredOrchestratingAggregate) -@ExperimentalContracts -fun Flow.handleItConcurrently( - numberOfActors: Int = 100, - actorsCapacity: Int = Channel.BUFFERED, - actorsStart: CoroutineStart = CoroutineStart.LAZY, - actorsContext: CoroutineContext = EmptyCoroutineContext, - partitionKey: (C) -> Int -): Flow = publishConcurrentlyTo( - this@StateStoredOrchestratingAggregate, - numberOfActors, - actorsCapacity, - actorsStart, - actorsContext, - partitionKey -) + * Alternative function to `context (IDecider, ISaga, StateLockingRepository) Flow.handleOptimistically()` + */ +context (StateOrchestratingComputation, StateLockingRepository) +@FlowPreview +suspend fun Flow.handleItOptimistically(): Flow> = + map { it.handleItOptimistically() } diff --git a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateContextualTest.kt b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateContextualTest.kt index b2c9e740..94c520c6 100644 --- a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateContextualTest.kt +++ b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateContextualTest.kt @@ -7,9 +7,7 @@ import com.fraktalio.fmodel.domain.examples.numbers.api.NumberCommand.EvenNumber import com.fraktalio.fmodel.domain.examples.numbers.api.NumberEvent.EvenNumberEvent.EvenNumberAdded import com.fraktalio.fmodel.domain.examples.numbers.api.NumberValue import com.fraktalio.fmodel.domain.examples.numbers.even.command.evenNumberDecider -import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec -import io.kotest.matchers.collections.shouldContainAll import io.kotest.matchers.collections.shouldContainExactly import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.flowOf @@ -55,89 +53,4 @@ class EventSourcedAggregateContextualTest : FunSpec({ } } - // version 1 - test("Event-sourced aggregate - concurrent and contextual - add even number") { - evenNumberRepository.deleteAll() - with(eventSourcingAggregate(evenDecider, evenNumberRepository)) { - flowOf( - AddEvenNumber(Description("desc"), NumberValue(6)), - AddEvenNumber(Description("desc"), NumberValue(4)) - ) - .handleConcurrently { it?.description.hashCode() } - .toList() shouldContainExactly listOf( - EvenNumberAdded(Description("desc"), NumberValue(6)), - EvenNumberAdded(Description("desc"), NumberValue(4)) - ) - } - } - // version 2 - test("Event-sourced decider and repository - concurrent and contextual - add even number") { - evenNumberRepository.deleteAll() - with(evenDecider) { - with(evenNumberRepository) { - flowOf( - AddEvenNumber(Description("desc"), NumberValue(6)), - AddEvenNumber(Description("desc"), NumberValue(4)) - ) - .handleConcurrently { it?.description.hashCode() } - .toList() shouldContainExactly listOf( - EvenNumberAdded(Description("desc"), NumberValue(6)), - EvenNumberAdded(Description("desc"), NumberValue(4)) - ) - } - } - } - // version 1 - test("Event-sourced aggregate - concurrent and contextual - add even number - different partition keys") { - evenNumberRepository.deleteAll() - // choosing command `value` hash as a partition key. It is not the same for these two commands. - with(eventSourcingAggregate(evenDecider, evenNumberRepository)) { - flowOf( - AddEvenNumber(Description("desc"), NumberValue(6)), - AddEvenNumber(Description("desc"), NumberValue(4)) - ) - .handleConcurrently { it?.description.hashCode() } - .toList() shouldContainAll listOf( - EvenNumberAdded(Description("desc"), NumberValue(4)), - EvenNumberAdded(Description("desc"), NumberValue(6)) - ) - } - } - // version 2 - test("Event-sourced decider and repository - concurrent and contextual - add even number - different partition keys") { - evenNumberRepository.deleteAll() - // choosing command `value` hash as a partition key. It is not the same for these two commands. - with(evenDecider) { - with(evenNumberRepository) { - flowOf( - AddEvenNumber(Description("desc"), NumberValue(6)), - AddEvenNumber(Description("desc"), NumberValue(4)) - ) - .handleConcurrently { it?.description.hashCode() } - .toList() shouldContainAll listOf( - EvenNumberAdded(Description("desc"), NumberValue(4)), - EvenNumberAdded(Description("desc"), NumberValue(6)) - ) - } - } - - } - - test("Event-sourced aggregate concurrent and contextual - add even number - exception (large number > 1000)") { - shouldThrow { - evenNumberRepository.deleteAll() - with(evenDecider) { - with(evenNumberRepository) { - flowOf( - AddEvenNumber(Description("2000"), NumberValue(2000)) - ) - .handleConcurrently { it?.description.hashCode() }.toList() shouldContainAll listOf( - EvenNumberAdded(Description("2000"), NumberValue(2000)) - ) - } - } - } - } - - }) diff --git a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualTest.kt b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualTest.kt index 910d644a..af1a7e2f 100644 --- a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualTest.kt +++ b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualTest.kt @@ -50,33 +50,4 @@ class MaterializedViewContextualTest : FunSpec({ ) } } - - test("Materialized view concurrent and contextual - even number added") { - evenNumberViewRepository.deleteAll() - with(evenView) { - with(evenNumberViewRepository) { - flowOf( - EvenNumberAdded(Description("EvenNumberAdded"), NumberValue(2)), - EvenNumberAdded(Description("EvenNumberAdded"), NumberValue(4)) - ).handleConcurrently { it?.description.hashCode() }.toList() shouldContainExactly listOf( - EvenNumberState(Description("Initial state, EvenNumberAdded"), NumberValue(2)), - EvenNumberState(Description("Initial state, EvenNumberAdded, EvenNumberAdded"), NumberValue(6)) - ) - } - } - } - - test("Materialized view concurrent and contextual materialized view interface - even number added") { - evenNumberViewRepository.deleteAll() - with(materializedView(evenView, evenNumberViewRepository)) { - flowOf( - EvenNumberAdded(Description("EvenNumberAdded"), NumberValue(2)), - EvenNumberAdded(Description("EvenNumberAdded"), NumberValue(4)) - ).handleConcurrently { it?.description.hashCode() }.toList() shouldContainExactly listOf( - EvenNumberState(Description("Initial state, EvenNumberAdded"), NumberValue(2)), - EvenNumberState(Description("Initial state, EvenNumberAdded, EvenNumberAdded"), NumberValue(6)) - ) - } - } - }) diff --git a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualTest.kt b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualTest.kt index 167f6938..6bfe65b4 100644 --- a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualTest.kt +++ b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualTest.kt @@ -52,34 +52,6 @@ class StateStoredAggregateContextualTest : FunSpec({ } } - test("State-stored aggregate concurrent and contextual - add even number") { - evenNumberStateRepository.deleteAll() - with(evenDecider) { - with(evenNumberStateRepository) { - flowOf( - AddEvenNumber(Description("desc"), NumberValue(6)), - AddEvenNumber(Description("desc"), NumberValue(4)) - ).handleConcurrently { it?.description.hashCode() }.toList() shouldContainExactly listOf( - EvenNumberState(Description("desc"), NumberValue(6)), - EvenNumberState(Description("desc"), NumberValue(10)) - ) - } - } - } - - test("State-stored aggregate concurrent and contextual with aggregate interface - add even number") { - evenNumberStateRepository.deleteAll() - with(stateStoredAggregate(evenDecider, evenNumberStateRepository)) { - flowOf( - AddEvenNumber(Description("desc"), NumberValue(6)), - AddEvenNumber(Description("desc"), NumberValue(4)) - ).handleConcurrently { it?.description.hashCode() }.toList() shouldContainExactly listOf( - EvenNumberState(Description("desc"), NumberValue(6)), - EvenNumberState(Description("desc"), NumberValue(10)) - ) - } - } - test("State-stored aggregate contextual - add even number - exception (large number > 1000)") { shouldThrow { diff --git a/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregate.kt b/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregate.kt index 1af4c0bf..b1489e7b 100644 --- a/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregate.kt +++ b/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregate.kt @@ -34,7 +34,6 @@ interface StateComputation : IDecider { * @param command of type [C] * @return The newly computed state of type [S] */ - @FlowPreview suspend fun S?.computeNewState(command: C): S { val currentState = this ?: initialState val events = decide(command, currentState) From 32502c4939baea23336405a1028c6178e5cb25a1 Mon Sep 17 00:00:00 2001 From: Ivan Dugalic Date: Wed, 28 Sep 2022 19:00:00 +0200 Subject: [PATCH 06/13] Tests improved --- .../fmodel/application/EventSourcedAggregateContextualTest.kt | 2 +- .../fmodel/application/MaterializedViewContextualTest.kt | 2 +- .../fmodel/application/StateStoredAggregateContextualTest.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateContextualTest.kt b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateContextualTest.kt index 94c520c6..3c0a38ee 100644 --- a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateContextualTest.kt +++ b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateContextualTest.kt @@ -30,7 +30,7 @@ class EventSourcedAggregateContextualTest : FunSpec({ flowOf( AddEvenNumber(Description("desc"), NumberValue(6)), AddEvenNumber(Description("desc"), NumberValue(4)) - ).handle().toList() shouldContainExactly listOf( + ).handleIt().toList() shouldContainExactly listOf( EvenNumberAdded(Description("desc"), NumberValue(6)), EvenNumberAdded(Description("desc"), NumberValue(4)) ) diff --git a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualTest.kt b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualTest.kt index af1a7e2f..a49c6627 100644 --- a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualTest.kt +++ b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualTest.kt @@ -44,7 +44,7 @@ class MaterializedViewContextualTest : FunSpec({ flowOf( EvenNumberAdded(Description("EvenNumberAdded"), NumberValue(2)), EvenNumberAdded(Description("EvenNumberAdded"), NumberValue(4)) - ).handle().toList() shouldContainExactly listOf( + ).handleIt().toList() shouldContainExactly listOf( EvenNumberState(Description("Initial state, EvenNumberAdded"), NumberValue(2)), EvenNumberState(Description("Initial state, EvenNumberAdded, EvenNumberAdded"), NumberValue(6)) ) diff --git a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualTest.kt b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualTest.kt index 6bfe65b4..81c8f85d 100644 --- a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualTest.kt +++ b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualTest.kt @@ -45,7 +45,7 @@ class StateStoredAggregateContextualTest : FunSpec({ flowOf( AddEvenNumber(Description("desc"), NumberValue(6)), AddEvenNumber(Description("desc"), NumberValue(4)) - ).handle().toList() shouldContainExactly listOf( + ).handleIt().toList() shouldContainExactly listOf( EvenNumberState(Description("desc"), NumberValue(6)), EvenNumberState(Description("desc"), NumberValue(10)) ) From bdc9d649a96f160b19696258188b0802948002fb Mon Sep 17 00:00:00 2001 From: Ivan Dugalic Date: Thu, 20 Oct 2022 20:26:51 +0200 Subject: [PATCH 07/13] Arrow and context-receivers: EffectScope --- application-arrow/build.gradle.kts | 1 + .../EventSourcingAggregateArrowExtension.kt | 10 +- .../MaterializedViewArrowExtension.kt | 116 +++----------- .../application/SagaManagerArrowExtension.kt | 2 +- .../StateStoredAggregateArrowExtension.kt | 126 +++------------- ...urcingAggregateArrowContextualExtension.kt | 68 +++++++++ ...aterializedViewArrowContextualExtension.kt | 50 ++++++ .../SagaManagerArrowContextualExtension.kt.kt | 23 +++ ...StoredAggregateArrowContextualExtension.kt | 72 +++++++++ ...ventSourcedAggregateArrowContextualTest.kt | 62 ++++++++ .../MaterializedViewArrowContextualTest.kt | 61 ++++++++ ...StateStoredAggregateArrowContextualTest.kt | 74 +++++++++ ...entSourcingAggregateContextualExtension.kt | 118 ++------------- .../MaterializedViewContextualExtension.kt | 91 +---------- ...StateStoredAggregateContextualExtension.kt | 142 ++---------------- .../EventSourcedAggregateContextualTest.kt | 4 +- .../MaterializedViewContextualTest.kt | 4 +- .../StateStoredAggregateContextualTest.kt | 7 +- application/build.gradle.kts | 1 + .../application/EventSourcingAggregate.kt | 15 ++ .../fmodel/application/MaterializedView.kt | 6 + .../fraktalio/fmodel/application/Result.kt | 16 +- .../application/StateStoredAggregate.kt | 14 ++ 23 files changed, 529 insertions(+), 554 deletions(-) create mode 100644 application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowContextualExtension.kt create mode 100644 application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualExtension.kt create mode 100644 application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowContextualExtension.kt.kt create mode 100644 application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualExtension.kt create mode 100644 application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateArrowContextualTest.kt create mode 100644 application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualTest.kt create mode 100644 application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualTest.kt diff --git a/application-arrow/build.gradle.kts b/application-arrow/build.gradle.kts index b932266e..481b8506 100644 --- a/application-arrow/build.gradle.kts +++ b/application-arrow/build.gradle.kts @@ -12,6 +12,7 @@ kotlin { compilations.all { kotlinOptions.jvmTarget = "1.8" kotlinOptions.verbose = true + kotlinOptions.freeCompilerArgs = kotlinOptions.freeCompilerArgs + "-Xcontext-receivers" } withJava() diff --git a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowExtension.kt b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowExtension.kt index 14c59e0d..0ed6a4e4 100644 --- a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowExtension.kt +++ b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowExtension.kt @@ -18,7 +18,7 @@ package com.fraktalio.fmodel.application import arrow.core.continuations.Effect import arrow.core.continuations.effect -import com.fraktalio.fmodel.application.Error.CommandHandlingFailed +import com.fraktalio.fmodel.application.Error.* import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.* @@ -99,25 +99,25 @@ fun EventSourcingLockingOrchestratingAggregate.handleOp fun EventSourcingAggregate.handleWithEffect(commands: Flow): Flow> = commands .flatMapConcat { handleWithEffect(it) } - .catch { emit(effect { shift(CommandHandlingFailed(it)) }) } + .catch { emit(effect { shift(CommandPublishingFailed(it)) }) } @FlowPreview fun EventSourcingOrchestratingAggregate.handleWithEffect(commands: Flow): Flow> = commands .flatMapConcat { handleWithEffect(it) } - .catch { emit(effect { shift(CommandHandlingFailed(it)) }) } + .catch { emit(effect { shift(CommandPublishingFailed(it)) }) } @FlowPreview fun EventSourcingLockingAggregate.handleOptimisticallyWithEffect(commands: Flow): Flow>> = commands .flatMapConcat { handleOptimisticallyWithEffect(it) } - .catch { emit(effect { shift(CommandHandlingFailed(it)) }) } + .catch { emit(effect { shift(CommandPublishingFailed(it)) }) } @FlowPreview fun EventSourcingLockingOrchestratingAggregate.handleOptimisticallyWithEffect(commands: Flow): Flow>> = commands .flatMapConcat { handleOptimisticallyWithEffect(it) } - .catch { emit(effect { shift(CommandHandlingFailed(it)) }) } + .catch { emit(effect { shift(CommandPublishingFailed(it)) }) } @FlowPreview fun C.publishWithEffect(aggregate: EventSourcingAggregate): Flow> = diff --git a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowExtension.kt b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowExtension.kt index b829e47f..6f7632d2 100644 --- a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowExtension.kt +++ b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowExtension.kt @@ -32,41 +32,14 @@ import kotlinx.coroutines.flow.map * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ -suspend fun I.handleWithEffect(event: E): Effect where I : ViewStateComputation, I : ViewStateRepository { - - fun S?.computeNewStateWithEffect(event: E): Effect = - effect { - try { - computeNewState(event) - } catch (t: Throwable) { - shift(CalculatingNewViewStateFailed(this@computeNewStateWithEffect, event, t.nonFatalOrThrow())) - } - } - - suspend fun E.fetchStateWithEffect(): Effect = - effect { - try { - fetchState() - } catch (t: Throwable) { - shift(FetchingViewStateFailed(this@fetchStateWithEffect, t.nonFatalOrThrow())) - } +suspend fun I.handleWithEffect(event: E): Effect where I : ViewStateComputation, I : ViewStateRepository = + effect { + try { + event.fetchState().computeNewState(event).save() + } catch (t: Throwable) { + shift(EventHandlingFailed(event, t.nonFatalOrThrow())) } - - suspend fun S.saveWithEffect(): Effect = - effect { - try { - save() - } catch (t: Throwable) { - shift(StoringStateFailed(this@saveWithEffect, t.nonFatalOrThrow())) - } - } - - return effect { - event.fetchStateWithEffect().bind() - .computeNewStateWithEffect(event).bind() - .saveWithEffect().bind() } -} /** * Extension function - Handles the event of type [E] @@ -76,41 +49,15 @@ suspend fun I.handleWithEffect(event: E): Effect where I : V * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ -suspend fun I.handleOptimisticallyWithEffect(event: E): Effect> where I : ViewStateComputation, I : ViewStateLockingRepository { - fun S?.computeNewStateWithEffect(event: E): Effect = - effect { - try { - computeNewState(event) - } catch (t: Throwable) { - shift(CalculatingNewViewStateFailed(this@computeNewStateWithEffect, event, t.nonFatalOrThrow())) - } - } - - suspend fun E.fetchStateWithEffect(): Effect> = - effect { - try { - fetchState() - } catch (t: Throwable) { - shift(FetchingViewStateFailed(this@fetchStateWithEffect, t.nonFatalOrThrow())) - } - } - - suspend fun S.saveWithEffect(currentVersion: V?): Effect> = - effect { - try { - save(currentVersion) - } catch (t: Throwable) { - shift(StoringStateFailed(this@saveWithEffect, t.nonFatalOrThrow())) - } +suspend fun I.handleOptimisticallyWithEffect(event: E): Effect> where I : ViewStateComputation, I : ViewStateLockingRepository = + effect { + try { + val (state, version) = event.fetchState() + state.computeNewState(event).save(version) + } catch (t: Throwable) { + shift(EventHandlingFailed(event, t.nonFatalOrThrow())) } - - return effect { - val (state, version) = event.fetchStateWithEffect().bind() - state - .computeNewStateWithEffect(event).bind() - .saveWithEffect(version).bind() } -} /** * Extension function - Handles the event of type [E] @@ -124,39 +71,14 @@ suspend fun I.handleOptimisticallyWithEffect(event: E): Effect I.handleOptimisticallyWithDeduplicationWithEffect(eventAndVersion: Pair): Effect> where I : ViewStateComputation, I : ViewStateLockingDeduplicationRepository { - fun S?.computeNewStateWithEffect(event: E): Effect = - effect { - try { - computeNewState(event) - } catch (t: Throwable) { - shift(CalculatingNewViewStateFailed(this@computeNewStateWithEffect, event, t.nonFatalOrThrow())) - } - } - - suspend fun E.fetchStateWithEffect(): Effect> = - effect { - try { - fetchState() - } catch (t: Throwable) { - shift(FetchingViewStateFailed(this@fetchStateWithEffect, t.nonFatalOrThrow())) - } - } - - suspend fun S.saveWithEffect(entityVersion: EV, currentStateVersion: SV?): Effect> = - effect { - try { - save(entityVersion, currentStateVersion) - } catch (t: Throwable) { - shift(StoringStateFailed(this@saveWithEffect, t.nonFatalOrThrow())) - } - } - return effect { val (event, eventVersion) = eventAndVersion - val (state, currentStateVersion) = event.fetchStateWithEffect().bind() - state - .computeNewStateWithEffect(event).bind() - .saveWithEffect(eventVersion, currentStateVersion).bind() + try { + val (state, currentStateVersion) = event.fetchState() + state.computeNewState(event).save(eventVersion, currentStateVersion) + } catch (t: Throwable) { + shift(EventHandlingFailed(event, t.nonFatalOrThrow())) + } } } diff --git a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowExtension.kt b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowExtension.kt index 33243480..5c2d151e 100644 --- a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowExtension.kt +++ b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowExtension.kt @@ -39,7 +39,7 @@ fun SagaManager.handleWithEffect(actionResult: AR): Flow { it } } - .catch { emit(effect { shift(ActionResultHandlingFailed(actionResult)) }) } + .catch { emit(effect { shift(ActionResultHandlingFailed(actionResult, it)) }) } /** * Extension function - Handles the [Flow] of action results of type [AR]. diff --git a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowExtension.kt b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowExtension.kt index efe6527b..f9d37443 100644 --- a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowExtension.kt +++ b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowExtension.kt @@ -19,11 +19,10 @@ package com.fraktalio.fmodel.application import arrow.core.continuations.Effect import arrow.core.continuations.effect import arrow.core.nonFatalOrThrow -import com.fraktalio.fmodel.application.Error.* +import com.fraktalio.fmodel.application.Error.CommandHandlingFailed +import com.fraktalio.fmodel.application.Error.CommandPublishingFailed import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.* /** * Extension function - Handles the command message of type [C] @@ -35,59 +34,15 @@ import kotlinx.coroutines.flow.map */ @FlowPreview suspend fun I.handleWithEffect(command: C): Effect where I : StateComputation, - I : StateRepository { - /** - * Inner function - Computes new State based on the previous State and the [command] or fails. - * - * @param command of type [C] - * @return [Effect] (either the newly computed state of type [S] or [Error]) - */ - suspend fun S?.computeNewStateWithEffect(command: C): Effect = - effect { - try { - computeNewState(command) - } catch (t: Throwable) { - shift(CalculatingNewStateFailed(this@computeNewStateWithEffect, command, t.nonFatalOrThrow())) - } - } - - /** - * Inner function - Fetch state - either version - * - * @receiver Command of type [C] - * @return [Effect] (either [Error] or the State of type [S]?) - */ - suspend fun C.fetchStateWithEffect(): Effect = - effect { - try { - fetchState() - } catch (t: Throwable) { - shift(FetchingStateFailed(this@fetchStateWithEffect, t.nonFatalOrThrow())) - } - } + I : StateRepository = + effect { + try { + command.fetchState().computeNewState(command).save() + } catch (t: Throwable) { + shift(CommandHandlingFailed(this, t.nonFatalOrThrow())) - /** - * Inner function - Save state - either version - * - * @receiver State of type [S] - * @return [Effect] (either [Error] or the newly saved State of type [S]) - */ - suspend fun S.saveWithEffect(): Effect = - effect { - try { - save() - } catch (t: Throwable) { - shift(StoringStateFailed(this@saveWithEffect, t.nonFatalOrThrow())) - } } - - return effect { - command - .fetchStateWithEffect().bind() - .computeNewStateWithEffect(command).bind() - .saveWithEffect().bind() } -} /** * Extension function - Handles the command message of type [C] to the locking state stored aggregate, optimistically @@ -99,60 +54,17 @@ suspend fun I.handleWithEffect(command: C): Effect where */ @FlowPreview suspend fun I.handleOptimisticallyWithEffect(command: C): Effect> where I : StateComputation, - I : StateLockingRepository { - /** - * Inner function - Computes new State based on the previous State and the [command] or fails. - * - * @param command of type [C] - * @return [Effect] (either the newly computed state of type [S] or [Error]) - */ - suspend fun S?.computeNewStateWithEffect(command: C): Effect = - effect { - try { - computeNewState(command) - } catch (t: Throwable) { - shift(CalculatingNewStateFailed(this@computeNewStateWithEffect, command, t.nonFatalOrThrow())) - } - } - - /** - * Inner function - Fetch state - either version - * - * @receiver Command of type [C] - * @return [Effect] (either [Error] or the State of type [S]?) - */ - suspend fun C.fetchStateWithEffect(): Effect> = - effect { - try { - fetchState() - } catch (t: Throwable) { - shift(FetchingStateFailed(this@fetchStateWithEffect, t.nonFatalOrThrow())) - } + I : StateLockingRepository = + effect { + try { + val (state, version) = command.fetchState() + state + .computeNewState(command) + .save(version) + } catch (t: Throwable) { + shift(CommandHandlingFailed(this, t.nonFatalOrThrow())) } - - /** - * Inner function - Save state - either version - * - * @receiver State of type [S] - * @return [Effect] (either [Error] or the newly saved State of type [S]) - */ - suspend fun S.saveWithEffect(currentStateVersion: V?): Effect> = - effect { - try { - save(currentStateVersion) - } catch (t: Throwable) { - shift(StoringStateFailed(this@saveWithEffect, t.nonFatalOrThrow())) - } - } - - return effect { - val (state, version) = command.fetchStateWithEffect().bind() - state - .computeNewStateWithEffect(command).bind() - .saveWithEffect(version).bind() } -} - /** * Extension function - Handles the [Flow] of command messages of type [C] @@ -235,5 +147,3 @@ fun Flow.publishWithEffect(aggregate: A): Flow> fun Flow.publishOptimisticallyWithEffect(aggregate: A): Flow>> where A : StateComputation, A : StateLockingRepository = aggregate.handleOptimisticallyWithEffect(this) - -private fun S.pairWith(version: V): Pair = Pair(this, version) \ No newline at end of file diff --git a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowContextualExtension.kt b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowContextualExtension.kt new file mode 100644 index 00000000..dd51dd2c --- /dev/null +++ b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowContextualExtension.kt @@ -0,0 +1,68 @@ +package com.fraktalio.fmodel.application + +import arrow.core.continuations.Effect +import arrow.core.continuations.effect +import com.fraktalio.fmodel.application.Error.CommandHandlingFailed +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.* + + +context (EventComputation, EventRepository) +@FlowPreview +fun C.handleWithEffect(): Flow> = + fetchEvents() + .computeNewEvents(this) + .save() + .map { effect { it } } + .catch { emit(effect { shift(CommandHandlingFailed(this@handleWithEffect)) }) } + +context (EventComputation, EventLockingRepository) +@FlowPreview +fun C.handleOptimisticallyWithEffect(): Flow>> = flow { + val events = this@handleOptimisticallyWithEffect.fetchEvents() + emitAll( + events.map { it.first } + .computeNewEvents(this@handleOptimisticallyWithEffect) + .save(events.lastOrNull()) + .map { effect> { it } } + .catch { emit(effect { shift(CommandHandlingFailed(this@handleOptimisticallyWithEffect)) }) } + ) +} + +context (EventOrchestratingComputation, EventRepository) +@FlowPreview +fun C.handleWithEffect(): Flow> = + fetchEvents() + .computeNewEventsByOrchestrating(this) { it.fetchEvents() } + .save() + .map { effect { it } } + .catch { emit(effect { shift(CommandHandlingFailed(this@handleWithEffect)) }) } + +context (EventOrchestratingComputation, EventLockingRepository) +@FlowPreview +fun C.handleOptimisticallyWithEffect(): Flow>> = + fetchEvents().map { it.first } + .computeNewEventsByOrchestrating(this) { it.fetchEvents().map { pair -> pair.first } } + .save(latestVersionProvider) + .map { effect> { it } } + .catch { emit(effect { shift(CommandHandlingFailed(this@handleOptimisticallyWithEffect)) }) } + +context (EventComputation, EventRepository) +@FlowPreview +fun Flow.handleWithEffect(): Flow> = + flatMapConcat { it.handleWithEffect() }.catch { emit(effect { shift(Error.CommandPublishingFailed(it)) }) } + +context (EventComputation, EventLockingRepository) +@FlowPreview +fun Flow.handleOptimisticallyWithEffect(): Flow>> = + flatMapConcat { it.handleOptimisticallyWithEffect() }.catch { emit(effect { shift(Error.CommandPublishingFailed(it)) }) } + +context (EventOrchestratingComputation, EventRepository) +@FlowPreview +fun Flow.handleWithEffect(): Flow> = + flatMapConcat { it.handleWithEffect() }.catch { emit(effect { shift(Error.CommandPublishingFailed(it)) }) } + +context (EventOrchestratingComputation, EventLockingRepository) +@FlowPreview +fun Flow.handleOptimisticallyWithEffect(): Flow>> = + flatMapConcat { it.handleOptimisticallyWithEffect() }.catch { emit(effect { shift(Error.CommandPublishingFailed(it)) }) } diff --git a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualExtension.kt b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualExtension.kt new file mode 100644 index 00000000..4ff20c39 --- /dev/null +++ b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualExtension.kt @@ -0,0 +1,50 @@ +package com.fraktalio.fmodel.application + +import arrow.core.continuations.Effect +import arrow.core.continuations.EffectScope +import arrow.core.continuations.effect +import arrow.core.nonFatalOrThrow +import com.fraktalio.fmodel.application.Error.EventHandlingFailed +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.map + + +context (ViewStateComputation, ViewStateRepository, EffectScope) +suspend fun E.handleWithEffect(): S = try { + fetchState().computeNewState(this).save() +} catch (t: Throwable) { + shift(EventHandlingFailed(this, t.nonFatalOrThrow())) +} + +context (ViewStateComputation, ViewStateLockingRepository, EffectScope) +suspend fun E.handleOptimisticallyWithEffect(): Pair = try { + val (state, version) = fetchState() + state.computeNewState(this).save(version) +} catch (t: Throwable) { + shift(EventHandlingFailed(this, t.nonFatalOrThrow())) +} + +context (ViewStateComputation, ViewStateLockingDeduplicationRepository, EffectScope) +suspend fun E.handleOptimisticallyWithDeduplicationAndEffect(eventAndVersion: Pair): Pair { + val (event, eventVersion) = eventAndVersion + return try { + val (state, stateVersion) = event.fetchState() + state.computeNewState(event).save(eventVersion, stateVersion) + } catch (t: Throwable) { + shift(EventHandlingFailed(this, t.nonFatalOrThrow())) + } +} + +context (ViewStateComputation, ViewStateRepository) +fun Flow.handleWithEffect(): Flow> = + map { effect { it.handleWithEffect() } }.catch { emit(effect { shift(Error.EventPublishingFailed(it)) }) } + +context (ViewStateComputation, ViewStateLockingRepository) +fun Flow.handleOptimisticallyWithEffect(): Flow>> = + map { effect { it.handleOptimisticallyWithEffect() } }.catch { emit(effect { shift(Error.EventPublishingFailed(it)) }) } + +context (ViewStateComputation, ViewStateLockingDeduplicationRepository) +fun Flow.handleOptimisticallyWithDeduplicationAndEffect(eventAndVersion: Pair): Flow>> = + map { effect { it.handleOptimisticallyWithDeduplicationAndEffect(eventAndVersion) } } + .catch { emit(effect { shift(Error.EventPublishingFailed(it)) }) } diff --git a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowContextualExtension.kt.kt b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowContextualExtension.kt.kt new file mode 100644 index 00000000..fb95201b --- /dev/null +++ b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowContextualExtension.kt.kt @@ -0,0 +1,23 @@ +package com.fraktalio.fmodel.application + +import arrow.core.continuations.Effect +import arrow.core.continuations.effect +import com.fraktalio.fmodel.application.Error.ActionResultHandlingFailed +import com.fraktalio.fmodel.application.Error.ActionResultPublishingFailed +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flatMapConcat +import kotlinx.coroutines.flow.map + +context (SagaManager) +fun AR.handleWithEffect(): Flow> = + computeNewActions() + .publish() + .map { effect { it } } + .catch { emit(effect { shift(ActionResultHandlingFailed(this@handleWithEffect, it)) }) } + +context (SagaManager) +@FlowPreview +fun Flow.handleWithEffect(): Flow> = + flatMapConcat { it.handleWithEffect() }.catch { emit(effect { shift(ActionResultPublishingFailed(it)) }) } diff --git a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualExtension.kt b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualExtension.kt new file mode 100644 index 00000000..c1dc2f6f --- /dev/null +++ b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualExtension.kt @@ -0,0 +1,72 @@ +package com.fraktalio.fmodel.application + +import arrow.core.continuations.Effect +import arrow.core.continuations.EffectScope +import arrow.core.continuations.effect +import arrow.core.nonFatalOrThrow +import com.fraktalio.fmodel.application.Error.CommandHandlingFailed +import com.fraktalio.fmodel.application.Error.CommandPublishingFailed + +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.map + +context (StateComputation, StateRepository, EffectScope) +suspend fun C.handleWithEffect(): S = + try { + fetchState().computeNewState(this).save() + } catch (t: Throwable) { + shift(CommandHandlingFailed(this, t.nonFatalOrThrow())) + } + +context (StateComputation, StateRepository) +fun Flow.handleWithEffect(): Flow> = + map { effect { it.handleWithEffect() } }.catch { emit(effect { shift(CommandPublishingFailed(it)) }) } + +context (StateComputation, StateLockingRepository, EffectScope) +suspend fun C.handleOptimisticallyWithEffect(): Pair = + try { + val (state, version) = this@handleOptimisticallyWithEffect.fetchState() + state + .computeNewState(this@handleOptimisticallyWithEffect) + .save(version) + } catch (t: Throwable) { + shift(CommandHandlingFailed(this, t.nonFatalOrThrow())) + } + +context (StateComputation, StateLockingRepository) +fun Flow.handleOptimisticallyWithEffect(): Flow>> = + map { effect { it.handleOptimisticallyWithEffect() } }.catch { emit(effect { shift(CommandPublishingFailed(it)) }) } + +context (StateOrchestratingComputation, StateRepository, EffectScope) +@FlowPreview +suspend fun C.handleWithEffect(): S = + try { + fetchState().computeNewState(this).save() + } catch (t: Throwable) { + shift(CommandHandlingFailed(this, t.nonFatalOrThrow())) + } + +context (StateOrchestratingComputation, StateRepository) +@FlowPreview +suspend fun Flow.handleWithEffect(): Flow> = + map { effect { it.handleWithEffect() } }.catch { emit(effect { shift(CommandPublishingFailed(it)) }) } + + +context (StateOrchestratingComputation, StateLockingRepository, EffectScope) +@FlowPreview +suspend fun C.handleOptimisticallyWithEffect(): Pair = + try { + val (state, version) = this@handleOptimisticallyWithEffect.fetchState() + state + .computeNewState(this@handleOptimisticallyWithEffect) + .save(version) + } catch (t: Throwable) { + shift(CommandHandlingFailed(this, t.nonFatalOrThrow())) + } + +context (StateOrchestratingComputation, StateLockingRepository) +@FlowPreview +suspend fun Flow.handleOptimisticallyWithEffect(): Flow>> = + map { effect { it.handleOptimisticallyWithEffect() } }.catch { emit(effect { shift(CommandPublishingFailed(it)) }) } diff --git a/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateArrowContextualTest.kt b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateArrowContextualTest.kt new file mode 100644 index 00000000..8acf8529 --- /dev/null +++ b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateArrowContextualTest.kt @@ -0,0 +1,62 @@ +package com.fraktalio.fmodel.application + +import arrow.core.Either +import arrow.core.Either.Left +import arrow.core.Either.Right +import arrow.core.continuations.Effect +import com.fraktalio.fmodel.application.Error.CommandPublishingFailed +import com.fraktalio.fmodel.application.examples.numbers.even.command.EvenNumberRepository +import com.fraktalio.fmodel.application.examples.numbers.even.command.evenNumberRepository +import com.fraktalio.fmodel.domain.examples.numbers.api.Description +import com.fraktalio.fmodel.domain.examples.numbers.api.NumberCommand.EvenNumberCommand +import com.fraktalio.fmodel.domain.examples.numbers.api.NumberCommand.EvenNumberCommand.AddEvenNumber +import com.fraktalio.fmodel.domain.examples.numbers.api.NumberEvent.EvenNumberEvent.EvenNumberAdded +import com.fraktalio.fmodel.domain.examples.numbers.api.NumberValue +import com.fraktalio.fmodel.domain.examples.numbers.even.command.evenNumberDecider +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.collections.shouldContainExactly +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.* +import kotlin.contracts.ExperimentalContracts + +private suspend infix fun Flow>.thenEvents(expected: Iterable>) = + map { it.toEither() }.toList() shouldContainExactly (expected) + +/** + * Event sourced aggregate contextual (context receivers) test + */ +@ExperimentalContracts +@FlowPreview +class EventSourcedAggregateArrowContextualTest : FunSpec({ + val evenDecider = evenNumberDecider() + val evenNumberRepository = evenNumberRepository() as EvenNumberRepository + + test("Event-sourced aggregate arrow contextual - add even number") { + evenNumberRepository.deleteAll() + with(eventSourcingAggregate(evenDecider, evenNumberRepository)) { + flowOf( + AddEvenNumber(Description("desc"), NumberValue(6)), + AddEvenNumber(Description("desc"), NumberValue(4)) + ).handleWithEffect() thenEvents ( + listOf( + Right(EvenNumberAdded(Description("desc"), NumberValue(6))), + Right(EvenNumberAdded(Description("desc"), NumberValue(4))) + ) + ) + } + } + test("Event-sourced aggregate arrow contextual - add even number") { + evenNumberRepository.deleteAll() + with(eventSourcingAggregate(evenDecider, evenNumberRepository)) { + val exception = IllegalStateException("Error") + flow { + emit(AddEvenNumber(Description("desc"), NumberValue(6))) + throw exception + }.handleWithEffect() thenEvents listOf( + Right(EvenNumberAdded(Description("desc"), NumberValue(6))), + Left(CommandPublishingFailed(exception)) + ) + } + } + +}) diff --git a/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualTest.kt b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualTest.kt new file mode 100644 index 00000000..98752bed --- /dev/null +++ b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualTest.kt @@ -0,0 +1,61 @@ +package com.fraktalio.fmodel.application + +import arrow.core.Either +import arrow.core.continuations.Effect +import com.fraktalio.fmodel.application.examples.numbers.even.query.EvenNumberViewRepository +import com.fraktalio.fmodel.application.examples.numbers.even.query.evenNumberViewRepository +import com.fraktalio.fmodel.domain.examples.numbers.api.Description +import com.fraktalio.fmodel.domain.examples.numbers.api.EvenNumberState +import com.fraktalio.fmodel.domain.examples.numbers.api.NumberEvent.EvenNumberEvent.EvenNumberAdded +import com.fraktalio.fmodel.domain.examples.numbers.api.NumberValue +import com.fraktalio.fmodel.domain.examples.numbers.even.query.evenNumberView +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf +import kotlin.contracts.ExperimentalContracts + +private suspend infix fun Effect.thenState(expected: S) { + val state = when (val result = this.toEither()) { + is Either.Right -> result.value + is Either.Left -> throw AssertionError("Expected Either.Right, but found Either.Left with value ${result.value}") + } + return state shouldBe expected +} + +/** + * Materialized View Contextual Test + */ +@FlowPreview +@ExperimentalContracts +class MaterializedViewArrowContextualTest : FunSpec({ + val evenView = evenNumberView() + val evenNumberViewRepository = evenNumberViewRepository() as EvenNumberViewRepository + + test("Materialized view arrow contextual - even number added") { + evenNumberViewRepository.deleteAll() + with(viewStateComputation(evenView)) { + with(evenNumberViewRepository) { + flowOf( + EvenNumberAdded(Description("EvenNumberAdded"), NumberValue(2)), + ).handleWithEffect().first() thenState EvenNumberState( + Description("Initial state, EvenNumberAdded"), + NumberValue(2) + ) + } + } + } + + test("Materialized view arrow contextual materialized view interface - even number added") { + evenNumberViewRepository.deleteAll() + with(materializedView(evenView, evenNumberViewRepository)) { + flowOf( + EvenNumberAdded(Description("EvenNumberAdded"), NumberValue(2)), + ).handleWithEffect().first() thenState EvenNumberState( + Description("Initial state, EvenNumberAdded"), + NumberValue(2) + ) + } + } +}) diff --git a/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualTest.kt b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualTest.kt new file mode 100644 index 00000000..7d29f279 --- /dev/null +++ b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualTest.kt @@ -0,0 +1,74 @@ +package com.fraktalio.fmodel.application + +import arrow.core.Either +import arrow.core.continuations.Effect +import com.fraktalio.fmodel.application.examples.numbers.even.command.EvenNumberStateRepository +import com.fraktalio.fmodel.application.examples.numbers.even.command.evenNumberStateRepository +import com.fraktalio.fmodel.domain.examples.numbers.api.Description +import com.fraktalio.fmodel.domain.examples.numbers.api.EvenNumberState +import com.fraktalio.fmodel.domain.examples.numbers.api.NumberCommand.EvenNumberCommand.AddEvenNumber +import com.fraktalio.fmodel.domain.examples.numbers.api.NumberValue +import com.fraktalio.fmodel.domain.examples.numbers.even.command.evenNumberDecider +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeInstanceOf +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf +import kotlin.contracts.ExperimentalContracts + +private suspend fun Effect.thenError() { + val error = when (val result = this.toEither()) { + is Either.Right -> throw AssertionError("Expected Either.Left, but found Either.Right with value ${result.value}") + is Either.Left -> result.value + } + error.shouldBeInstanceOf() +} + +private suspend infix fun Effect.thenState(expected: S) { + val state = when (val result = this.toEither()) { + is Either.Right -> result.value + is Either.Left -> throw AssertionError("Expected Either.Right, but found Either.Left with value ${result.value}") + } + return state shouldBe expected +} + +/** + * State-stored aggregate arrow, contextual test + */ +@ExperimentalContracts +@FlowPreview +class StateStoredAggregateArrowContextualTest : FunSpec({ + val evenDecider = evenNumberDecider() + val evenNumberStateRepository = evenNumberStateRepository() as EvenNumberStateRepository + + test("State-stored aggregatearrow contextual - add even number") { + evenNumberStateRepository.deleteAll() + with(stateComputation(evenDecider)) { + with(evenNumberStateRepository) { + flowOf( + AddEvenNumber(Description("desc"), NumberValue(6)) + ).handleWithEffect().first() thenState EvenNumberState(Description("desc"), NumberValue(6)) + } + } + } + + test("State-stored aggregate arrow contextual with aggregate interface - add even number") { + evenNumberStateRepository.deleteAll() + with(stateStoredAggregate(evenDecider, evenNumberStateRepository)) { + flowOf( + AddEvenNumber(Description("desc"), NumberValue(6)) + ).handleWithEffect().first() thenState EvenNumberState(Description("desc"), NumberValue(6)) + } + } + + test("State-stored aggregate arrow contextual - add even number - exception (large number > 1000)") { + with(stateComputation(evenDecider)) { + with(evenNumberStateRepository) { + flowOf( + AddEvenNumber(Description("desc"), NumberValue(6000)) + ).handleWithEffect().first().thenError() + } + } + } +}) diff --git a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateContextualExtension.kt b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateContextualExtension.kt index d1f579bd..93b8edfb 100644 --- a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateContextualExtension.kt +++ b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateContextualExtension.kt @@ -1,23 +1,9 @@ package com.fraktalio.fmodel.application -import com.fraktalio.fmodel.domain.IDecider -import com.fraktalio.fmodel.domain.ISaga import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.* -/** - * Handle command - Event-sourced aggregate/decider - * @receiver [IDecider] - context receiver - * @receiver [EventRepository] - context receiver - * @receiver command of type C to be handled - */ -context (IDecider, EventRepository) -fun C.handle(): Flow = - with(object : EventComputation, IDecider by this@IDecider {}) { - this@handle.handleIt() - } - /** * Handle command - Event-sourced aggregate/decider * @receiver [EventComputation] - context receiver @@ -25,20 +11,8 @@ fun C.handle(): Flow = * @receiver command of type C to be handled */ context (EventComputation, EventRepository) -fun C.handleIt(): Flow = fetchEvents().computeNewEvents(this).save() +fun C.handle(): Flow = fetchEvents().computeNewEvents(this).save() -/** - * Handle command optimistically - Event-sourced locking aggregate/decider - * @receiver [IDecider] - context receiver - * @receiver [EventLockingRepository] - context receiver - * @receiver command of type C to be handled - */ -context (IDecider, EventLockingRepository) -@FlowPreview -fun C.handleOptimistically(): Flow> = - with(object : EventComputation, IDecider by this@IDecider {}) { - this@handleOptimistically.handleItOptimistically() - } /** * Handle command optimistically - Event-sourced locking aggregate/decider @@ -48,30 +22,15 @@ fun C.handleOptimistically(): Flow> = */ context (EventComputation, EventLockingRepository) @FlowPreview -fun C.handleItOptimistically(): Flow> = flow { - val events = this@handleItOptimistically.fetchEvents() +fun C.handleOptimistically(): Flow> = flow { + val events = this@handleOptimistically.fetchEvents() emitAll( events.map { it.first } - .computeNewEvents(this@handleItOptimistically) + .computeNewEvents(this@handleOptimistically) .save(events.lastOrNull()) ) } -/** - * Handle command - Event-sourced orchestrating aggregate/decider - * @receiver [IDecider] - context receiver - * @receiver [ISaga] - context receiver - * @receiver [EventRepository] - context receiver - * @receiver command of type C to be handled - */ -context (IDecider, ISaga, EventRepository) -@FlowPreview -fun C.handle(): Flow = - with(object : EventOrchestratingComputation, IDecider by this@IDecider, - ISaga by this@ISaga {}) { - this@handle.handleIt() - } - /** * Handle command - Event-sourced orchestrating aggregate/decider * @receiver [EventOrchestratingComputation] - context receiver @@ -80,22 +39,7 @@ fun C.handle(): Flow = */ context (EventOrchestratingComputation, EventRepository) @FlowPreview -fun C.handleIt(): Flow = fetchEvents().computeNewEventsByOrchestrating(this) { it.fetchEvents() }.save() - -/** - * Handle command optimistically - Event-sourced orchestrating locking aggregate/decider - * @receiver [IDecider] - context receiver - * @receiver [ISaga] - context receiver - * @receiver [EventLockingRepository] - context receiver - * @receiver command of type C to be handled - */ -context (IDecider, ISaga, EventLockingRepository) -@FlowPreview -fun C.handleOptimistically(): Flow> = - with(object : EventOrchestratingComputation, IDecider by this@IDecider, - ISaga by this@ISaga {}) { - this@handleOptimistically.handleItOptimistically() - } +fun C.handle(): Flow = fetchEvents().computeNewEventsByOrchestrating(this) { it.fetchEvents() }.save() /** * Handle command optimistically - Event-sourced orchestrating locking aggregate/decider @@ -105,22 +49,12 @@ fun C.handleOptimistically(): Flow> = */ context (EventOrchestratingComputation, EventLockingRepository) @FlowPreview -fun C.handleItOptimistically(): Flow> = +fun C.handleOptimistically(): Flow> = this .fetchEvents().map { it.first } .computeNewEventsByOrchestrating(this) { it.fetchEvents().map { pair -> pair.first } } .save(latestVersionProvider) -/** - * Handle command(s) - Event-sourced aggregate/decider - * @receiver [IDecider] - context receiver - * @receiver [EventRepository] - context receiver - * @receiver commands of type Flow to be handled - */ -context (IDecider, EventRepository) -@FlowPreview -fun Flow.handle(): Flow = flatMapConcat { it.handle() } - /** * Handle command(s) - Event-sourced aggregate/decider * @receiver [EventComputation] - context receiver @@ -129,17 +63,7 @@ fun Flow.handle(): Flow = flatMapConcat { it.handle() } */ context (EventComputation, EventRepository) @FlowPreview -fun Flow.handleIt(): Flow = flatMapConcat { it.handleIt() } - -/** - * Handle command(s) optimistically - Event-sourced locking aggregate/decider - * @receiver [IDecider] - context receiver - * @receiver [EventLockingRepository] - context receiver - * @receiver commands of type Flow to be handled - */ -context (IDecider, EventLockingRepository) -@FlowPreview -fun Flow.handleOptimistically(): Flow> = flatMapConcat { it.handleOptimistically() } +fun Flow.handle(): Flow = flatMapConcat { it.handle() } /** * Handle command(s) optimistically - Event-sourced locking aggregate/decider @@ -149,18 +73,7 @@ fun Flow.handleOptimistically(): Flow> = flatMapConca */ context (EventComputation, EventLockingRepository) @FlowPreview -fun Flow.handleItOptimistically(): Flow> = flatMapConcat { it.handleItOptimistically() } - -/** - * Handle command(s) - Event-sourced orchestrating aggregate/decider - * @receiver [IDecider] - context receiver - * @receiver [ISaga] - context receiver - * @receiver [EventRepository] - context receiver - * @receiver commands of type Flow to be handled - */ -context (IDecider, ISaga, EventRepository) -@FlowPreview -fun Flow.handle(): Flow = flatMapConcat { it.handle() } +fun Flow.handleOptimistically(): Flow> = flatMapConcat { it.handleOptimistically() } /** * Handle command(s) - Event-sourced orchestrating aggregate/decider @@ -170,18 +83,7 @@ fun Flow.handle(): Flow = flatMapConcat { it.handle() } */ context (EventOrchestratingComputation, EventRepository) @FlowPreview -fun Flow.handleIt(): Flow = flatMapConcat { it.handleIt() } - -/** - * Handle command(s) optimistically - Event-sourced orchestrating locking aggregate/decider - * @receiver [IDecider] - context receiver - * @receiver [ISaga] - context receiver - * @receiver [EventLockingRepository] - context receiver - * @receiver commands of type Flow to be handled - */ -context (IDecider, ISaga, EventLockingRepository) -@FlowPreview -fun Flow.handleOptimistically(): Flow> = flatMapConcat { it.handleOptimistically() } +fun Flow.handle(): Flow = flatMapConcat { it.handle() } /** * Handle command(s) optimistically - Event-sourced orchestrating locking aggregate/decider @@ -191,4 +93,4 @@ fun Flow.handleOptimistically(): Flow> = flatMapConca */ context (EventOrchestratingComputation, EventLockingRepository) @FlowPreview -fun Flow.handleItOptimistically(): Flow> = flatMapConcat { it.handleItOptimistically() } +fun Flow.handleOptimistically(): Flow> = flatMapConcat { it.handleOptimistically() } diff --git a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt index 044d449a..e0f8b028 100644 --- a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt +++ b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt @@ -1,55 +1,17 @@ package com.fraktalio.fmodel.application -import com.fraktalio.fmodel.domain.IView import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -/** - * Handle event - Materialized View - * @receiver [IView] - context receiver - * @receiver [ViewStateRepository] - context receiver - * @receiver event of type E to be handled - */ -context (IView, ViewStateRepository) -suspend fun E.handle(): S = - with(object : ViewStateComputation, IView by this@IView {}) { - this@handle.handleIt() - } - -/** - * Handle event - Materialized View - * @receiver [IView] - context receiver - * @receiver [ViewStateLockingRepository] - context receiver - * @receiver event of type E to be handled - */ -context (IView, ViewStateLockingRepository) -suspend fun E.handleOptimistically(): Pair = - with(object : ViewStateComputation, IView by this@IView {}) { - this@handleOptimistically.handleItOptimistically() - } - -/** - * Handle event - Materialized View - * @receiver [IView] - context receiver - * @receiver [ViewStateLockingDeduplicationRepository] - context receiver - * @receiver event of type E to be handled - */ -context (IView, ViewStateLockingDeduplicationRepository) -suspend fun E.handleOptimisticallyWithDeduplication(eventAndVersion: Pair): Pair = - with(object : ViewStateComputation, IView by this@IView {}) { - this@handleOptimisticallyWithDeduplication.handleItOptimisticallyWithDeduplication(eventAndVersion) - } /** * Handle event - Materialized View * @receiver [ViewStateComputation] - context receiver * @receiver [ViewStateRepository] - context receiver * @receiver event of type E to be handled - * - * Alternative function to `context (IView, ViewStateRepository) E.handle()` */ context (ViewStateComputation, ViewStateRepository) -suspend fun E.handleIt(): S = fetchState().computeNewState(this).save() +suspend fun E.handle(): S = fetchState().computeNewState(this).save() /** @@ -57,14 +19,12 @@ suspend fun E.handleIt(): S = fetchState().computeNewState(this).save() * @receiver [ViewStateComputation] - context receiver * @receiver [ViewStateLockingRepository] - context receiver * @receiver event of type E to be handled - * - * Alternative function to `context (IView, ViewStateLockingRepository) E.handleOptimistically()` */ context (ViewStateComputation, ViewStateLockingRepository) -suspend fun E.handleItOptimistically(): Pair { - val (state, version) = this@handleItOptimistically.fetchState() +suspend fun E.handleOptimistically(): Pair { + val (state, version) = this@handleOptimistically.fetchState() return state - .computeNewState(this@handleItOptimistically) + .computeNewState(this@handleOptimistically) .save(version) } @@ -73,11 +33,9 @@ suspend fun E.handleItOptimistically(): Pair { * @receiver [ViewStateComputation] - context receiver * @receiver [ViewStateLockingDeduplicationRepository] - context receiver * @receiver event of type E to be handled - * - * Alternative function to `context (IView, ViewStateLockingDeduplicationRepository) E.handleOptimisticallyWithDeduplication()` */ context (ViewStateComputation, ViewStateLockingDeduplicationRepository) -suspend fun E.handleItOptimisticallyWithDeduplication(eventAndVersion: Pair): Pair { +suspend fun E.handleOptimisticallyWithDeduplication(eventAndVersion: Pair): Pair { val (event, eventVersion) = eventAndVersion val (state, currentStateVersion) = event.fetchState() return state @@ -85,62 +43,29 @@ suspend fun E.handleItOptimisticallyWithDeduplication(eventAndVer .save(eventVersion, currentStateVersion) } -/** - * Handle event(s) - Materialized View - * @receiver [IView] - context receiver - * @receiver [ViewStateRepository] - context receiver - * @receiver events of type Flow to be handled - */ -context (IView, ViewStateRepository) -fun Flow.handle(): Flow = map { it.handle() } - -/** - * Handle event(s) - Materialized View - * @receiver [IView] - context receiver - * @receiver [ViewStateLockingRepository] - context receiver - * @receiver events of type Flow to be handled - */ -context (IView, ViewStateLockingRepository) -fun Flow.handleOptimistically(): Flow> = map { it.handleOptimistically() } - -/** - * Handle event(s) - Materialized View - * @receiver [IView] - context receiver - * @receiver [ViewStateLockingDeduplicationRepository] - context receiver - * @receiver events of type Flow to be handled - */ -context (IView, ViewStateLockingDeduplicationRepository) -fun Flow.handleOptimisticallyWithDeduplication(eventAndVersion: Pair): Flow> = map { it.handleOptimisticallyWithDeduplication(eventAndVersion) } - /** * Handle event(s) - Materialized View * @receiver [ViewStateComputation] - context receiver * @receiver [ViewStateRepository] - context receiver * @receiver events of type Flow to be handled - * - * Alternative function to `context (IView, ViewStateRepository) Flow.handle()` */ context (ViewStateComputation, ViewStateRepository) -fun Flow.handleIt(): Flow = map { it.handleIt() } +fun Flow.handle(): Flow = map { it.handle() } /** * Handle event(s) - Materialized View * @receiver [ViewStateComputation] - context receiver * @receiver [ViewStateLockingRepository] - context receiver * @receiver events of type Flow to be handled - * - * Alternative function to `context (IView, ViewStateLockingRepository) Flow.handleOptimistically()` */ context (ViewStateComputation, ViewStateLockingRepository) -fun Flow.handleItOptimistically(): Flow> = map { it.handleItOptimistically() } +fun Flow.handleOptimistically(): Flow> = map { it.handleOptimistically() } /** * Handle event(s) - Materialized View * @receiver [ViewStateComputation] - context receiver * @receiver [ViewStateLockingDeduplicationRepository] - context receiver * @receiver events of type Flow to be handled - * - * Alternative function to `context (IView, ViewStateLockingDeduplicationRepository) Flow.handleOptimisticallyWithDeduplication(eventAndVersion)` */ context (ViewStateComputation, ViewStateLockingDeduplicationRepository) -fun Flow.handleItOptimisticallyWithDeduplication(eventAndVersion: Pair): Flow> = map { it.handleItOptimisticallyWithDeduplication(eventAndVersion) } +fun Flow.handleOptimisticallyWithDeduplication(eventAndVersion: Pair): Flow> = map { it.handleOptimisticallyWithDeduplication(eventAndVersion) } diff --git a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualExtension.kt b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualExtension.kt index cbb7989e..8c4cbba4 100644 --- a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualExtension.kt +++ b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualExtension.kt @@ -1,91 +1,29 @@ package com.fraktalio.fmodel.application -import com.fraktalio.fmodel.domain.IDecider -import com.fraktalio.fmodel.domain.ISaga import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -/** - * Handle command - State-stored aggregate/decider - * @receiver [IDecider] - context receiver - * @receiver [StateRepository] - context receiver - * @receiver command of type C to be handled - */ -context (IDecider, StateRepository) -suspend fun C.handle(): S = - with(object : StateComputation, IDecider by this@IDecider {}) { - this@handle.handleIt() - } - -/** - * Handle command - State-stored aggregate/decider - * @receiver [IDecider] - context receiver - * @receiver [StateLockingRepository] - context receiver - * @receiver command of type C to be handled - */ -context (IDecider, StateLockingRepository) -suspend fun C.handleOptimistically(): Pair = - with(object : StateComputation, IDecider by this@IDecider {}) { - this@handleOptimistically.handleItOptimistically() - } - -/** - * Handle command - State-stored orchestrating aggregate/decider - * @receiver [IDecider] - context receiver - * @receiver [ISaga] - context receiver - * @receiver [StateRepository] - context receiver - * @receiver command of type C to be handled - */ -context (IDecider, ISaga, StateRepository) -@FlowPreview -suspend fun C.handle(): S = - with(object : StateOrchestratingComputation, - IDecider by this@IDecider, - ISaga by this@ISaga {}) { - this@handle.handleIt() - } - -/** - * Handle command - State-stored orchestrating aggregate/decider - * @receiver [IDecider] - context receiver - * @receiver [ISaga] - context receiver - * @receiver [StateLockingRepository] - context receiver - * @receiver command of type C to be handled - */ -context (IDecider, ISaga, StateLockingRepository) -@FlowPreview -suspend fun C.handleOptimistically(): Pair = - with(object : StateOrchestratingComputation, - IDecider by this@IDecider, - ISaga by this@ISaga {}) { - this@handleOptimistically.handleItOptimistically() - } - /** * Handle command - State-stored aggregate/decider * @receiver [StateComputation] - context receiver * @receiver [StateRepository] - context receiver * @receiver command of type C to be handled - * - * Alternative function to `context (IDecider, StateRepository) C.handle()` */ context (StateComputation, StateRepository) -suspend fun C.handleIt(): S = fetchState().computeNewState(this).save() +suspend fun C.handle(): S = fetchState().computeNewState(this).save() /** * Handle command - State-stored aggregate/decider * @receiver [StateComputation] - context receiver * @receiver [StateLockingRepository] - context receiver * @receiver command of type C to be handled - * - * Alternative function to `context (IDecider, StateLockingRepository) C.handleOptimistically()` */ context (StateComputation, StateLockingRepository) -suspend fun C.handleItOptimistically(): Pair { - val (state, version) = this@handleItOptimistically.fetchState() +suspend fun C.handleOptimistically(): Pair { + val (state, version) = this@handleOptimistically.fetchState() return state - .computeNewState(this@handleItOptimistically) + .computeNewState(this@handleOptimistically) .save(version) } @@ -94,93 +32,43 @@ suspend fun C.handleItOptimistically(): Pair { * @receiver [StateOrchestratingComputation] - context receiver * @receiver [StateRepository] - context receiver * @receiver command of type C to be handled - * - * Alternative function to `context (IDecider, ISaga, StateRepository) C.handle()` */ context (StateOrchestratingComputation, StateRepository) @FlowPreview -suspend fun C.handleIt(): S = fetchState().computeNewState(this).save() +suspend fun C.handle(): S = fetchState().computeNewState(this).save() /** * Handle command - State-stored orchestrating aggregate/decider * @receiver [StateOrchestratingComputation] - context receiver * @receiver [StateLockingRepository] - context receiver * @receiver command of type C to be handled - * - * Alternative function to `context (IDecider, ISaga, StateLockingRepository) C.handleOptimistically()` */ context (StateOrchestratingComputation, StateLockingRepository) @FlowPreview -suspend fun C.handleItOptimistically(): Pair { - val (state, version) = this@handleItOptimistically.fetchState() +suspend fun C.handleOptimistically(): Pair { + val (state, version) = this@handleOptimistically.fetchState() return state - .computeNewState(this@handleItOptimistically) + .computeNewState(this@handleOptimistically) .save(version) } - -/** - * Handle command(s) - State-stored aggregate/decider - * @receiver [IDecider] - context receiver - * @receiver [StateRepository] - context receiver - * @receiver commands of type `Flow` to be handled - */ -context (IDecider, StateRepository) -fun Flow.handle(): Flow = map { it.handle() } - -/** - * Handle command(s) - State-stored aggregate/decider - * @receiver [IDecider] - context receiver - * @receiver [StateLockingRepository] - context receiver - * @receiver commands of type `Flow` to be handled - */ -context (IDecider, StateLockingRepository) -fun Flow.handleOptimistically(): Flow> = map { it.handleOptimistically() } - - -/** - * Handle command(s) - State-stored orchestrating aggregate/decider - * @receiver [IDecider] - context receiver - * @receiver [ISaga] - context receiver - * @receiver [StateRepository] - context receiver - * @receiver commands of type `Flow` to be handled - */ -context (IDecider, ISaga, StateRepository) -@FlowPreview -suspend fun Flow.handle(): Flow = map { it.handle() } - -/** - * Handle command(s) - State-stored orchestrating aggregate/decider - * @receiver [IDecider] - context receiver - * @receiver [ISaga] - context receiver - * @receiver [StateLockingRepository] - context receiver - * @receiver commands of type `Flow` to be handled - */ -context (IDecider, ISaga, StateLockingRepository) -@FlowPreview -suspend fun Flow.handleOptimistically(): Flow> = map { it.handleOptimistically() } - /** * Handle command(s) - State-stored aggregate/decider * @receiver [StateComputation] - context receiver * @receiver [StateRepository] - context receiver * @receiver commands of type `Flow` to be handled - * - * Alternative function to `context (IDecider, StateRepository) Flow.handle()` */ context (StateComputation, StateRepository) -fun Flow.handleIt(): Flow = map { it.handleIt() } +fun Flow.handle(): Flow = map { it.handle() } /** * Handle command(s) - State-stored aggregate/decider * @receiver [StateComputation] - context receiver * @receiver [StateLockingRepository] - context receiver * @receiver commands of type `Flow` to be handled - * - * Alternative function to `context (IDecider, StateLockingRepository) Flow.handleOptimistically()` */ context (StateComputation, StateLockingRepository) -fun Flow.handleItOptimistically(): Flow> = map { it.handleItOptimistically() } +fun Flow.handleOptimistically(): Flow> = map { it.handleOptimistically() } /** @@ -188,22 +76,18 @@ fun Flow.handleItOptimistically(): Flow> = map { it.h * @receiver [StateOrchestratingComputation] - context receiver * @receiver [StateRepository] - context receiver * @receiver commands of type `Flow` to be handled - * - * Alternative function to `context (IDecider, ISaga, StateRepository) Flow.handle()` */ context (StateOrchestratingComputation, StateRepository) @FlowPreview -suspend fun Flow.handleIt(): Flow = map { it.handleIt() } +suspend fun Flow.handle(): Flow = map { it.handle() } /** * Handle command(s) - State-stored orchestrating aggregate/decider * @receiver [StateOrchestratingComputation] - context receiver * @receiver [StateLockingRepository] - context receiver * @receiver commands of type `Flow` to be handled - * - * Alternative function to `context (IDecider, ISaga, StateLockingRepository) Flow.handleOptimistically()` */ context (StateOrchestratingComputation, StateLockingRepository) @FlowPreview -suspend fun Flow.handleItOptimistically(): Flow> = - map { it.handleItOptimistically() } +suspend fun Flow.handleOptimistically(): Flow> = + map { it.handleOptimistically() } diff --git a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateContextualTest.kt b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateContextualTest.kt index 3c0a38ee..0ab397e6 100644 --- a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateContextualTest.kt +++ b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateContextualTest.kt @@ -30,7 +30,7 @@ class EventSourcedAggregateContextualTest : FunSpec({ flowOf( AddEvenNumber(Description("desc"), NumberValue(6)), AddEvenNumber(Description("desc"), NumberValue(4)) - ).handleIt().toList() shouldContainExactly listOf( + ).handle().toList() shouldContainExactly listOf( EvenNumberAdded(Description("desc"), NumberValue(6)), EvenNumberAdded(Description("desc"), NumberValue(4)) ) @@ -40,7 +40,7 @@ class EventSourcedAggregateContextualTest : FunSpec({ // version 2 test("Event-sourced decider and repository - contextual - add even number") { evenNumberRepository.deleteAll() - with(evenDecider) { + with(eventComputation(evenDecider)) { with(evenNumberRepository) { flowOf( AddEvenNumber(Description("desc"), NumberValue(6)), diff --git a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualTest.kt b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualTest.kt index a49c6627..c9cbfe65 100644 --- a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualTest.kt +++ b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualTest.kt @@ -25,7 +25,7 @@ class MaterializedViewContextualTest : FunSpec({ test("Materialized view contextual - even number added") { evenNumberViewRepository.deleteAll() - with(evenView) { + with(viewStateComputation(evenView)) { with(evenNumberViewRepository) { flowOf( EvenNumberAdded(Description("EvenNumberAdded"), NumberValue(2)), @@ -44,7 +44,7 @@ class MaterializedViewContextualTest : FunSpec({ flowOf( EvenNumberAdded(Description("EvenNumberAdded"), NumberValue(2)), EvenNumberAdded(Description("EvenNumberAdded"), NumberValue(4)) - ).handleIt().toList() shouldContainExactly listOf( + ).handle().toList() shouldContainExactly listOf( EvenNumberState(Description("Initial state, EvenNumberAdded"), NumberValue(2)), EvenNumberState(Description("Initial state, EvenNumberAdded, EvenNumberAdded"), NumberValue(6)) ) diff --git a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualTest.kt b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualTest.kt index 81c8f85d..3bd78257 100644 --- a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualTest.kt +++ b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateContextualTest.kt @@ -26,7 +26,7 @@ class StateStoredAggregateContextualTest : FunSpec({ test("State-stored aggregate contextual - add even number") { evenNumberStateRepository.deleteAll() - with(evenDecider) { + with(stateComputation(evenDecider)) { with(evenNumberStateRepository) { flowOf( AddEvenNumber(Description("desc"), NumberValue(6)), @@ -45,7 +45,7 @@ class StateStoredAggregateContextualTest : FunSpec({ flowOf( AddEvenNumber(Description("desc"), NumberValue(6)), AddEvenNumber(Description("desc"), NumberValue(4)) - ).handleIt().toList() shouldContainExactly listOf( + ).handle().toList() shouldContainExactly listOf( EvenNumberState(Description("desc"), NumberValue(6)), EvenNumberState(Description("desc"), NumberValue(10)) ) @@ -54,8 +54,7 @@ class StateStoredAggregateContextualTest : FunSpec({ test("State-stored aggregate contextual - add even number - exception (large number > 1000)") { shouldThrow { - - with(evenDecider) { + with(stateComputation(evenDecider)) { with(evenNumberStateRepository) { flowOf( AddEvenNumber(Description("desc"), NumberValue(6000)) diff --git a/application/build.gradle.kts b/application/build.gradle.kts index a6ef1c58..69687930 100644 --- a/application/build.gradle.kts +++ b/application/build.gradle.kts @@ -25,6 +25,7 @@ kotlin { arch == "aarch64" || arch.startsWith("arm") -> macosArm64() else -> macosX64() } + hostOs == "Linux" -> linuxX64() isMingwX64 -> mingwX64() else -> throw GradleException("Host OS is not supported in Kotlin/Native.") diff --git a/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregate.kt b/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregate.kt index 43104ebc..24ab96ab 100644 --- a/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregate.kt +++ b/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregate.kt @@ -243,5 +243,20 @@ fun eventSourcingLockingOrchestratingAggregate( ): EventSourcingLockingOrchestratingAggregate = object : EventSourcingLockingOrchestratingAggregate, EventLockingRepository by eventRepository, + IDecider by decider, + ISaga by saga {} + + +fun eventComputation( + decider: IDecider +): EventComputation = + object : EventComputation, + IDecider by decider {} + +fun eventOrchestratingComputation( + decider: IDecider, + saga: ISaga +): EventOrchestratingComputation = + object : EventOrchestratingComputation, IDecider by decider, ISaga by saga {} \ No newline at end of file diff --git a/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/MaterializedView.kt b/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/MaterializedView.kt index 3744feca..6c626c5f 100644 --- a/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/MaterializedView.kt +++ b/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/MaterializedView.kt @@ -138,4 +138,10 @@ fun materializedLockingDeduplicationView( ): MaterializedLockingDeduplicationView = object : MaterializedLockingDeduplicationView, ViewStateLockingDeduplicationRepository by viewStateRepository, + IView by view {} + +fun viewStateComputation( + view: IView, +): ViewStateComputation = + object : ViewStateComputation, IView by view {} \ No newline at end of file diff --git a/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/Result.kt b/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/Result.kt index f1d51e58..7dc47c66 100644 --- a/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/Result.kt +++ b/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/Result.kt @@ -39,22 +39,8 @@ sealed class Error : Result() { data class EventPublishingFailed(override val throwable: Throwable? = null) : Error() data class ActionResultPublishingFailed(override val throwable: Throwable? = null) : Error() data class CommandHandlingFailed(val command: C, override val throwable: Throwable? = null) : Error() + data class EventHandlingFailed(val event: E, override val throwable: Throwable? = null) : Error() data class ActionResultHandlingFailed(val actionResult: AR, override val throwable: Throwable? = null) : Error() - data class FetchingStateFailed(val command: C, override val throwable: Throwable? = null) : Error() - data class FetchingViewStateFailed(val event: E, override val throwable: Throwable? = null) : Error() - data class CalculatingNewStateFailed( - val state: S, - val command: C, - override val throwable: Throwable? = null - ) : Error() - - data class CalculatingNewViewStateFailed( - val state: S, - val event: E, - override val throwable: Throwable? = null - ) : Error() - - data class StoringStateFailed(val state: S, override val throwable: Throwable? = null) : Error() } diff --git a/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregate.kt b/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregate.kt index b1489e7b..1a53efed 100644 --- a/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregate.kt +++ b/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregate.kt @@ -256,5 +256,19 @@ fun stateStoredLockingOrchestratingAggregate( ): StateStoredLockingOrchestratingAggregate = object : StateStoredLockingOrchestratingAggregate, StateLockingRepository by stateRepository, + IDecider by decider, + ISaga by saga {} + +fun stateComputation( + decider: IDecider +): StateComputation = + object : StateComputation, + IDecider by decider {} + +fun stateOrchestratingComputation( + decider: IDecider, + saga: ISaga +): StateOrchestratingComputation = + object : StateOrchestratingComputation, IDecider by decider, ISaga by saga {} \ No newline at end of file From 4aed8706e214176b1a3c6aa8ff1fd6a097a57ce8 Mon Sep 17 00:00:00 2001 From: Ivan Dugalic Date: Thu, 20 Oct 2022 22:13:29 +0200 Subject: [PATCH 08/13] Arrow and context-receivers: EffectScope --- .../application/EventSourcedAggregateArrowContextualTest.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateArrowContextualTest.kt b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateArrowContextualTest.kt index 8acf8529..f3729ab7 100644 --- a/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateArrowContextualTest.kt +++ b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateArrowContextualTest.kt @@ -35,11 +35,9 @@ class EventSourcedAggregateArrowContextualTest : FunSpec({ evenNumberRepository.deleteAll() with(eventSourcingAggregate(evenDecider, evenNumberRepository)) { flowOf( - AddEvenNumber(Description("desc"), NumberValue(6)), AddEvenNumber(Description("desc"), NumberValue(4)) ).handleWithEffect() thenEvents ( listOf( - Right(EvenNumberAdded(Description("desc"), NumberValue(6))), Right(EvenNumberAdded(Description("desc"), NumberValue(4))) ) ) From a81cada3381eb7fe2380601b47becca6171e5831 Mon Sep 17 00:00:00 2001 From: Ivan Dugalic Date: Thu, 20 Oct 2022 22:24:05 +0200 Subject: [PATCH 09/13] Arrow and context-receivers: EffectScope --- .../fmodel/application/EventSourcedAggregateContextualTest.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateContextualTest.kt b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateContextualTest.kt index 0ab397e6..d6244957 100644 --- a/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateContextualTest.kt +++ b/application-vanilla/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateContextualTest.kt @@ -28,10 +28,8 @@ class EventSourcedAggregateContextualTest : FunSpec({ evenNumberRepository.deleteAll() with(eventSourcingAggregate(evenDecider, evenNumberRepository)) { flowOf( - AddEvenNumber(Description("desc"), NumberValue(6)), AddEvenNumber(Description("desc"), NumberValue(4)) ).handle().toList() shouldContainExactly listOf( - EvenNumberAdded(Description("desc"), NumberValue(6)), EvenNumberAdded(Description("desc"), NumberValue(4)) ) } @@ -43,10 +41,8 @@ class EventSourcedAggregateContextualTest : FunSpec({ with(eventComputation(evenDecider)) { with(evenNumberRepository) { flowOf( - AddEvenNumber(Description("desc"), NumberValue(6)), AddEvenNumber(Description("desc"), NumberValue(4)) ).handle().toList() shouldContainExactly listOf( - EvenNumberAdded(Description("desc"), NumberValue(6)), EvenNumberAdded(Description("desc"), NumberValue(4)) ) } From 04f318a4ecf319ceb96cfe842dfdec5e5b27042b Mon Sep 17 00:00:00 2001 From: Ivan Dugalic Date: Thu, 5 Jan 2023 13:20:59 +0100 Subject: [PATCH 10/13] Upgrading `arrow-kotlin` to 2.0.0-SNAPSHOT --- application-arrow/build.gradle.kts | 1 + .../EventSourcingAggregateArrowExtension.kt | 19 +++---- .../MaterializedViewArrowExtension.kt | 29 +++++------ .../application/SagaManagerArrowExtension.kt | 4 +- .../StateStoredAggregateArrowExtension.kt | 22 ++++---- .../application/EventSourcedAggregateTest.kt | 1 + .../application/MaterializedViewTest.kt | 1 + .../application/StateStoredAggregateTest.kt | 1 + .../domain/examples/numbers/NumberSaga.kt | 6 +++ .../numbers/even/command/EvenNumberDecider.kt | 4 ++ .../numbers/even/query/EvenNumberView.kt | 2 + .../numbers/odd/command/OddNumberDecider.kt | 4 ++ .../numbers/odd/query/OddNumberView.kt | 2 + ...urcingAggregateArrowContextualExtension.kt | 16 +++--- ...aterializedViewArrowContextualExtension.kt | 39 +++++++-------- .../SagaManagerArrowContextualExtension.kt.kt | 4 +- ...StoredAggregateArrowContextualExtension.kt | 50 +++++++++---------- ...ventSourcedAggregateArrowContextualTest.kt | 1 + .../MaterializedViewArrowContextualTest.kt | 1 + ...StateStoredAggregateArrowContextualTest.kt | 1 + application-vanilla/build.gradle.kts | 1 + .../domain/examples/numbers/NumberSaga.kt | 4 ++ .../numbers/even/command/EvenNumberDecider.kt | 4 ++ .../numbers/even/query/EvenNumberView.kt | 2 + .../numbers/odd/command/OddNumberDecider.kt | 4 ++ .../numbers/odd/query/OddNumberView.kt | 2 + .../MaterializedViewContextualExtension.kt | 3 +- .../SagaManagerContextualExtension.kt | 1 + build.gradle.kts | 1 + domain/build.gradle.kts | 1 + gradle/libs.versions.toml | 4 +- 31 files changed, 141 insertions(+), 94 deletions(-) diff --git a/application-arrow/build.gradle.kts b/application-arrow/build.gradle.kts index 481b8506..e56fdc35 100644 --- a/application-arrow/build.gradle.kts +++ b/application-arrow/build.gradle.kts @@ -49,6 +49,7 @@ kotlin { arch == "aarch64" || arch.startsWith("arm") -> macosArm64() else -> macosX64() } + hostOs == "Linux" -> linuxX64() isMingwX64 -> mingwX64() else -> throw GradleException("Host OS is not supported in Kotlin/Native.") diff --git a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowExtension.kt b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowExtension.kt index 0ed6a4e4..bc231c2c 100644 --- a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowExtension.kt +++ b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowExtension.kt @@ -18,7 +18,8 @@ package com.fraktalio.fmodel.application import arrow.core.continuations.Effect import arrow.core.continuations.effect -import com.fraktalio.fmodel.application.Error.* +import com.fraktalio.fmodel.application.Error.CommandHandlingFailed +import com.fraktalio.fmodel.application.Error.CommandPublishingFailed import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.* @@ -37,7 +38,7 @@ fun EventSourcingAggregate.handleWithEffect(command: C): Flow .computeNewEvents(command) .save() .map { effect { it } } - .catch { emit(effect { shift(CommandHandlingFailed(command)) }) } + .catch { emit(effect { raise(CommandHandlingFailed(command)) }) } /** * Extension function - Handles the command message of type [C] @@ -54,7 +55,7 @@ fun EventSourcingOrchestratingAggregate.handleWithEffect(comm .computeNewEventsByOrchestrating(command) { it.fetchEvents() } .save() .map { effect { it } } - .catch { emit(effect { shift(CommandHandlingFailed(command)) }) } + .catch { emit(effect { raise(CommandHandlingFailed(command)) }) } /** * Extension function - Handles the command message of type [C] @@ -73,7 +74,7 @@ fun EventSourcingLockingAggregate.handleOptimisticallyW .computeNewEvents(command) .save(events.lastOrNull()) .map { effect> { it } } - .catch { emit(effect { shift(CommandHandlingFailed(command)) }) } + .catch { emit(effect { raise(CommandHandlingFailed(command)) }) } ) } @@ -92,32 +93,32 @@ fun EventSourcingLockingOrchestratingAggregate.handleOp .computeNewEventsByOrchestrating(command) { it.fetchEvents().map { pair -> pair.first } } .save(latestVersionProvider) .map { effect> { it } } - .catch { emit(effect { shift(CommandHandlingFailed(command)) }) } + .catch { emit(effect { raise(CommandHandlingFailed(command)) }) } @FlowPreview fun EventSourcingAggregate.handleWithEffect(commands: Flow): Flow> = commands .flatMapConcat { handleWithEffect(it) } - .catch { emit(effect { shift(CommandPublishingFailed(it)) }) } + .catch { emit(effect { raise(CommandPublishingFailed(it)) }) } @FlowPreview fun EventSourcingOrchestratingAggregate.handleWithEffect(commands: Flow): Flow> = commands .flatMapConcat { handleWithEffect(it) } - .catch { emit(effect { shift(CommandPublishingFailed(it)) }) } + .catch { emit(effect { raise(CommandPublishingFailed(it)) }) } @FlowPreview fun EventSourcingLockingAggregate.handleOptimisticallyWithEffect(commands: Flow): Flow>> = commands .flatMapConcat { handleOptimisticallyWithEffect(it) } - .catch { emit(effect { shift(CommandPublishingFailed(it)) }) } + .catch { emit(effect { raise(CommandPublishingFailed(it)) }) } @FlowPreview fun EventSourcingLockingOrchestratingAggregate.handleOptimisticallyWithEffect(commands: Flow): Flow>> = commands .flatMapConcat { handleOptimisticallyWithEffect(it) } - .catch { emit(effect { shift(CommandPublishingFailed(it)) }) } + .catch { emit(effect { raise(CommandPublishingFailed(it)) }) } @FlowPreview fun C.publishWithEffect(aggregate: EventSourcingAggregate): Flow> = diff --git a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowExtension.kt b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowExtension.kt index 6f7632d2..b77390d7 100644 --- a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowExtension.kt +++ b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowExtension.kt @@ -17,9 +17,10 @@ package com.fraktalio.fmodel.application import arrow.core.continuations.Effect +import arrow.core.continuations.catch import arrow.core.continuations.effect -import arrow.core.nonFatalOrThrow -import com.fraktalio.fmodel.application.Error.* +import com.fraktalio.fmodel.application.Error.EventHandlingFailed +import com.fraktalio.fmodel.application.Error.EventPublishingFailed import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.map @@ -34,10 +35,10 @@ import kotlinx.coroutines.flow.map */ suspend fun I.handleWithEffect(event: E): Effect where I : ViewStateComputation, I : ViewStateRepository = effect { - try { + catch({ event.fetchState().computeNewState(event).save() - } catch (t: Throwable) { - shift(EventHandlingFailed(event, t.nonFatalOrThrow())) + }) { + raise(EventHandlingFailed(event, it)) } } @@ -51,11 +52,11 @@ suspend fun I.handleWithEffect(event: E): Effect where I : V */ suspend fun I.handleOptimisticallyWithEffect(event: E): Effect> where I : ViewStateComputation, I : ViewStateLockingRepository = effect { - try { + catch({ val (state, version) = event.fetchState() state.computeNewState(event).save(version) - } catch (t: Throwable) { - shift(EventHandlingFailed(event, t.nonFatalOrThrow())) + }) { + raise(EventHandlingFailed(event, it)) } } @@ -73,11 +74,11 @@ suspend fun I.handleOptimisticallyWithEffect(event: E): Effect I.handleOptimisticallyWithDeduplicationWithEffect(eventAndVersion: Pair): Effect> where I : ViewStateComputation, I : ViewStateLockingDeduplicationRepository { return effect { val (event, eventVersion) = eventAndVersion - try { + catch({ val (state, currentStateVersion) = event.fetchState() state.computeNewState(event).save(eventVersion, currentStateVersion) - } catch (t: Throwable) { - shift(EventHandlingFailed(event, t.nonFatalOrThrow())) + }) { + raise(EventHandlingFailed(event, it)) } } } @@ -93,7 +94,7 @@ suspend fun I.handleOptimisticallyWithDeduplicationWithEffect( fun I.handleWithEffect(events: Flow): Flow> where I : ViewStateComputation, I : ViewStateRepository = events .map { handleWithEffect(it) } - .catch { emit(effect { shift(EventPublishingFailed(it)) }) } + .catch { emit(effect { raise(EventPublishingFailed(it)) }) } /** * Extension function - Handles the flow of events of type [E] @@ -106,7 +107,7 @@ fun I.handleWithEffect(events: Flow): Flow> where fun I.handleOptimisticallyWithEffect(events: Flow): Flow>> where I : ViewStateComputation, I : ViewStateLockingRepository = events .map { handleOptimisticallyWithEffect(it) } - .catch { emit(effect { shift(EventPublishingFailed(it)) }) } + .catch { emit(effect { raise(EventPublishingFailed(it)) }) } /** * Extension function - Handles the flow of events of type [Pair]<[E], [EV]> @@ -119,7 +120,7 @@ fun I.handleOptimisticallyWithEffect(events: Flow): Flow I.handleOptimisticallyWithDeduplicationWithEffect(eventsAndVersions: Flow>): Flow>> where I : ViewStateComputation, I : ViewStateLockingDeduplicationRepository = eventsAndVersions .map { handleOptimisticallyWithDeduplicationWithEffect(it) } - .catch { emit(effect { shift(EventPublishingFailed(it)) }) } + .catch { emit(effect { raise(EventPublishingFailed(it)) }) } /** diff --git a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowExtension.kt b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowExtension.kt index 5c2d151e..ab832ebc 100644 --- a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowExtension.kt +++ b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowExtension.kt @@ -39,7 +39,7 @@ fun SagaManager.handleWithEffect(actionResult: AR): Flow { it } } - .catch { emit(effect { shift(ActionResultHandlingFailed(actionResult, it)) }) } + .catch { emit(effect { raise(ActionResultHandlingFailed(actionResult, it)) }) } /** * Extension function - Handles the [Flow] of action results of type [AR]. @@ -53,7 +53,7 @@ fun SagaManager.handleWithEffect(actionResult: AR): Flow SagaManager.handleWithEffect(actionResults: Flow): Flow> = actionResults .flatMapConcat { handleWithEffect(it) } - .catch { emit(effect { shift(ActionResultPublishingFailed(it)) }) } + .catch { emit(effect { raise(ActionResultPublishingFailed(it)) }) } /** diff --git a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowExtension.kt b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowExtension.kt index f9d37443..ef89be55 100644 --- a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowExtension.kt +++ b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowExtension.kt @@ -17,12 +17,14 @@ package com.fraktalio.fmodel.application import arrow.core.continuations.Effect +import arrow.core.continuations.catch import arrow.core.continuations.effect -import arrow.core.nonFatalOrThrow import com.fraktalio.fmodel.application.Error.CommandHandlingFailed import com.fraktalio.fmodel.application.Error.CommandPublishingFailed import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.map /** * Extension function - Handles the command message of type [C] @@ -36,10 +38,10 @@ import kotlinx.coroutines.flow.* suspend fun I.handleWithEffect(command: C): Effect where I : StateComputation, I : StateRepository = effect { - try { + catch({ command.fetchState().computeNewState(command).save() - } catch (t: Throwable) { - shift(CommandHandlingFailed(this, t.nonFatalOrThrow())) + }) { + raise(CommandHandlingFailed(this@handleWithEffect, it)) } } @@ -56,13 +58,13 @@ suspend fun I.handleWithEffect(command: C): Effect where suspend fun I.handleOptimisticallyWithEffect(command: C): Effect> where I : StateComputation, I : StateLockingRepository = effect { - try { + catch({ val (state, version) = command.fetchState() state .computeNewState(command) .save(version) - } catch (t: Throwable) { - shift(CommandHandlingFailed(this, t.nonFatalOrThrow())) + }) { + raise(CommandHandlingFailed(this@handleOptimisticallyWithEffect, it)) } } @@ -79,7 +81,7 @@ fun I.handleWithEffect(commands: Flow): Flow> w I : StateRepository = commands .map { handleWithEffect(it) } - .catch { emit(effect { shift(CommandPublishingFailed(it)) }) } + .catch { emit(effect { raise(CommandPublishingFailed(it)) }) } /** * Extension function - Handles the [Flow] of command messages of type [C] to the locking state stored aggregate, optimistically @@ -94,7 +96,7 @@ fun I.handleOptimisticallyWithEffect(commands: Flow): Flow = commands .map { handleOptimisticallyWithEffect(it) } - .catch { emit(effect { shift(CommandPublishingFailed(it)) }) } + .catch { emit(effect { raise(CommandPublishingFailed(it)) }) } /** * Extension function - Publishes the command of type [C] to the state stored aggregate diff --git a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateTest.kt b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateTest.kt index 17cc2e0e..03b53ccb 100644 --- a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateTest.kt +++ b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateTest.kt @@ -4,6 +4,7 @@ import arrow.core.Either import arrow.core.Either.Left import arrow.core.Either.Right import arrow.core.continuations.Effect +import arrow.core.continuations.toEither import com.fraktalio.fmodel.application.examples.numbers.NumberRepository import com.fraktalio.fmodel.application.examples.numbers.even.command.EvenNumberLockingRepository import com.fraktalio.fmodel.application.examples.numbers.even.command.EvenNumberRepository diff --git a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewTest.kt b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewTest.kt index 6b90b6d5..e4d37d43 100644 --- a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewTest.kt +++ b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewTest.kt @@ -2,6 +2,7 @@ package com.fraktalio.fmodel.application import arrow.core.Either import arrow.core.continuations.Effect +import arrow.core.continuations.toEither import com.fraktalio.fmodel.application.examples.numbers.NumberViewRepository import com.fraktalio.fmodel.application.examples.numbers.even.query.EvenNumberLockingViewRepository import com.fraktalio.fmodel.application.examples.numbers.even.query.EvenNumberViewRepository diff --git a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateTest.kt b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateTest.kt index c178d024..b4584c6b 100644 --- a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateTest.kt +++ b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateTest.kt @@ -2,6 +2,7 @@ package com.fraktalio.fmodel.application import arrow.core.Either import arrow.core.continuations.Effect +import arrow.core.continuations.toEither import com.fraktalio.fmodel.application.examples.numbers.NumberStateRepository import com.fraktalio.fmodel.application.examples.numbers.even.command.EvenNumberLockingStateRepository import com.fraktalio.fmodel.application.examples.numbers.even.command.EvenNumberStateRepository diff --git a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/NumberSaga.kt b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/NumberSaga.kt index 64c65296..2440937a 100644 --- a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/NumberSaga.kt +++ b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/NumberSaga.kt @@ -49,24 +49,28 @@ fun numberSaga() = Saga( NumberValue(numberEvent.value.get - 1) ) ) + is EvenNumberSubtracted -> flowOf( SubtractOddNumber( Description("${numberEvent.value.get - 1}"), NumberValue(numberEvent.value.get - 1) ) ) + is OddNumberAdded -> flowOf( AddEvenNumber( Description("${numberEvent.value.get + 1}"), NumberValue(numberEvent.value.get + 1) ) ) + is OddNumberSubtracted -> flowOf( SubtractEvenNumber( Description("${numberEvent.value.get + 1}"), NumberValue(numberEvent.value.get + 1) ) ) + else -> emptyFlow() } } @@ -99,12 +103,14 @@ fun oddNumberSaga() = Saga flowOf( SubtractEvenNumber( Description("${numberEvent.value.get + 1}"), NumberValue(numberEvent.value.get + 1) ) ) + else -> emptyFlow() } } diff --git a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/even/command/EvenNumberDecider.kt b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/even/command/EvenNumberDecider.kt index 4ee89d33..0f91b481 100644 --- a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/even/command/EvenNumberDecider.kt +++ b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/even/command/EvenNumberDecider.kt @@ -55,12 +55,14 @@ fun evenNumberDecider(): Decider flowOf( EvenNumberSubtracted( c.description, c.value ) ) + null -> emptyFlow() } }, @@ -70,10 +72,12 @@ fun evenNumberDecider(): Decider EvenNumberState( e.description, NumberValue(s.value.get - e.value.get) ) + null -> s } } diff --git a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/even/query/EvenNumberView.kt b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/even/query/EvenNumberView.kt index f63d2b23..d983c9be 100644 --- a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/even/query/EvenNumberView.kt +++ b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/even/query/EvenNumberView.kt @@ -41,10 +41,12 @@ fun evenNumberView(): View = View( Description(evenNumberState.description.get + ", " + e.description.get), NumberValue(evenNumberState.value.get + e.value.get) ) + e is EvenNumberSubtracted && (evenNumberState != null) -> EvenNumberState( Description(evenNumberState.description.get + ", " + e.description.get), NumberValue(evenNumberState.value.get - e.value.get) ) + else -> evenNumberState } } diff --git a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/odd/command/OddNumberDecider.kt b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/odd/command/OddNumberDecider.kt index 49e926a8..d57442dc 100644 --- a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/odd/command/OddNumberDecider.kt +++ b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/odd/command/OddNumberDecider.kt @@ -49,12 +49,14 @@ fun oddNumberDecider(): Decider flowOf( OddNumberSubtracted( c.description, c.value ) ) + null -> emptyFlow() } }, @@ -64,10 +66,12 @@ fun oddNumberDecider(): Decider OddNumberState( e.description, NumberValue(s.value.get - e.value.get) ) + null -> s } } diff --git a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/odd/query/OddNumberView.kt b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/odd/query/OddNumberView.kt index 0c889cd6..622fe8c5 100644 --- a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/odd/query/OddNumberView.kt +++ b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/odd/query/OddNumberView.kt @@ -41,10 +41,12 @@ fun oddNumberView(): View = View( Description(oddNumberState.description.get + ", " + e.description.get), NumberValue(oddNumberState.value.get + e.value.get) ) + e is OddNumberSubtracted && (oddNumberState != null) -> OddNumberState( Description(oddNumberState.description.get + ", " + e.description.get), NumberValue(oddNumberState.value.get - e.value.get) ) + else -> oddNumberState } } diff --git a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowContextualExtension.kt b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowContextualExtension.kt index dd51dd2c..a36a6431 100644 --- a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowContextualExtension.kt +++ b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowContextualExtension.kt @@ -14,7 +14,7 @@ fun C.handleWithEffect(): Flow> = .computeNewEvents(this) .save() .map { effect { it } } - .catch { emit(effect { shift(CommandHandlingFailed(this@handleWithEffect)) }) } + .catch { emit(effect { raise(CommandHandlingFailed(this@handleWithEffect)) }) } context (EventComputation, EventLockingRepository) @FlowPreview @@ -25,7 +25,7 @@ fun C.handleOptimisticallyWithEffect(): Flow> { it } } - .catch { emit(effect { shift(CommandHandlingFailed(this@handleOptimisticallyWithEffect)) }) } + .catch { emit(effect { raise(CommandHandlingFailed(this@handleOptimisticallyWithEffect)) }) } ) } @@ -36,7 +36,7 @@ fun C.handleWithEffect(): Flow> = .computeNewEventsByOrchestrating(this) { it.fetchEvents() } .save() .map { effect { it } } - .catch { emit(effect { shift(CommandHandlingFailed(this@handleWithEffect)) }) } + .catch { emit(effect { raise(CommandHandlingFailed(this@handleWithEffect)) }) } context (EventOrchestratingComputation, EventLockingRepository) @FlowPreview @@ -45,24 +45,24 @@ fun C.handleOptimisticallyWithEffect(): Flow pair.first } } .save(latestVersionProvider) .map { effect> { it } } - .catch { emit(effect { shift(CommandHandlingFailed(this@handleOptimisticallyWithEffect)) }) } + .catch { emit(effect { raise(CommandHandlingFailed(this@handleOptimisticallyWithEffect)) }) } context (EventComputation, EventRepository) @FlowPreview fun Flow.handleWithEffect(): Flow> = - flatMapConcat { it.handleWithEffect() }.catch { emit(effect { shift(Error.CommandPublishingFailed(it)) }) } + flatMapConcat { it.handleWithEffect() }.catch { emit(effect { raise(Error.CommandPublishingFailed(it)) }) } context (EventComputation, EventLockingRepository) @FlowPreview fun Flow.handleOptimisticallyWithEffect(): Flow>> = - flatMapConcat { it.handleOptimisticallyWithEffect() }.catch { emit(effect { shift(Error.CommandPublishingFailed(it)) }) } + flatMapConcat { it.handleOptimisticallyWithEffect() }.catch { emit(effect { raise(Error.CommandPublishingFailed(it)) }) } context (EventOrchestratingComputation, EventRepository) @FlowPreview fun Flow.handleWithEffect(): Flow> = - flatMapConcat { it.handleWithEffect() }.catch { emit(effect { shift(Error.CommandPublishingFailed(it)) }) } + flatMapConcat { it.handleWithEffect() }.catch { emit(effect { raise(Error.CommandPublishingFailed(it)) }) } context (EventOrchestratingComputation, EventLockingRepository) @FlowPreview fun Flow.handleOptimisticallyWithEffect(): Flow>> = - flatMapConcat { it.handleOptimisticallyWithEffect() }.catch { emit(effect { shift(Error.CommandPublishingFailed(it)) }) } + flatMapConcat { it.handleOptimisticallyWithEffect() }.catch { emit(effect { raise(Error.CommandPublishingFailed(it)) }) } diff --git a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualExtension.kt b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualExtension.kt index 4ff20c39..09ea7a62 100644 --- a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualExtension.kt +++ b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualExtension.kt @@ -1,50 +1,47 @@ package com.fraktalio.fmodel.application -import arrow.core.continuations.Effect -import arrow.core.continuations.EffectScope -import arrow.core.continuations.effect -import arrow.core.nonFatalOrThrow +import arrow.core.continuations.* import com.fraktalio.fmodel.application.Error.EventHandlingFailed import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.map -context (ViewStateComputation, ViewStateRepository, EffectScope) -suspend fun E.handleWithEffect(): S = try { - fetchState().computeNewState(this).save() -} catch (t: Throwable) { - shift(EventHandlingFailed(this, t.nonFatalOrThrow())) +context (ViewStateComputation, ViewStateRepository, Raise) +suspend fun E.handleWithEffect(): S = catch({ + fetchState().computeNewState(this@handleWithEffect).save() +}) { + raise(EventHandlingFailed(this@handleWithEffect, it)) } -context (ViewStateComputation, ViewStateLockingRepository, EffectScope) -suspend fun E.handleOptimisticallyWithEffect(): Pair = try { +context (ViewStateComputation, ViewStateLockingRepository, Raise) +suspend fun E.handleOptimisticallyWithEffect(): Pair = catch({ val (state, version) = fetchState() - state.computeNewState(this).save(version) -} catch (t: Throwable) { - shift(EventHandlingFailed(this, t.nonFatalOrThrow())) + state.computeNewState(this@handleOptimisticallyWithEffect).save(version) +}) { + raise(EventHandlingFailed(this@handleOptimisticallyWithEffect, it)) } -context (ViewStateComputation, ViewStateLockingDeduplicationRepository, EffectScope) +context (ViewStateComputation, ViewStateLockingDeduplicationRepository, Raise) suspend fun E.handleOptimisticallyWithDeduplicationAndEffect(eventAndVersion: Pair): Pair { val (event, eventVersion) = eventAndVersion - return try { + return catch({ val (state, stateVersion) = event.fetchState() state.computeNewState(event).save(eventVersion, stateVersion) - } catch (t: Throwable) { - shift(EventHandlingFailed(this, t.nonFatalOrThrow())) + }) { + raise(EventHandlingFailed(this@handleOptimisticallyWithDeduplicationAndEffect, it)) } } context (ViewStateComputation, ViewStateRepository) fun Flow.handleWithEffect(): Flow> = - map { effect { it.handleWithEffect() } }.catch { emit(effect { shift(Error.EventPublishingFailed(it)) }) } + map { effect { it.handleWithEffect() } }.catch { emit(effect { raise(Error.EventPublishingFailed(it)) }) } context (ViewStateComputation, ViewStateLockingRepository) fun Flow.handleOptimisticallyWithEffect(): Flow>> = - map { effect { it.handleOptimisticallyWithEffect() } }.catch { emit(effect { shift(Error.EventPublishingFailed(it)) }) } + map { effect { it.handleOptimisticallyWithEffect() } }.catch { emit(effect { raise(Error.EventPublishingFailed(it)) }) } context (ViewStateComputation, ViewStateLockingDeduplicationRepository) fun Flow.handleOptimisticallyWithDeduplicationAndEffect(eventAndVersion: Pair): Flow>> = map { effect { it.handleOptimisticallyWithDeduplicationAndEffect(eventAndVersion) } } - .catch { emit(effect { shift(Error.EventPublishingFailed(it)) }) } + .catch { emit(effect { raise(Error.EventPublishingFailed(it)) }) } diff --git a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowContextualExtension.kt.kt b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowContextualExtension.kt.kt index fb95201b..f83d7ff6 100644 --- a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowContextualExtension.kt.kt +++ b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowContextualExtension.kt.kt @@ -15,9 +15,9 @@ fun AR.handleWithEffect(): Flow> = computeNewActions() .publish() .map { effect { it } } - .catch { emit(effect { shift(ActionResultHandlingFailed(this@handleWithEffect, it)) }) } + .catch { emit(effect { raise(ActionResultHandlingFailed(this@handleWithEffect, it)) }) } context (SagaManager) @FlowPreview fun Flow.handleWithEffect(): Flow> = - flatMapConcat { it.handleWithEffect() }.catch { emit(effect { shift(ActionResultPublishingFailed(it)) }) } + flatMapConcat { it.handleWithEffect() }.catch { emit(effect { raise(ActionResultPublishingFailed(it)) }) } diff --git a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualExtension.kt b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualExtension.kt index c1dc2f6f..16785870 100644 --- a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualExtension.kt +++ b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualExtension.kt @@ -1,72 +1,72 @@ package com.fraktalio.fmodel.application import arrow.core.continuations.Effect -import arrow.core.continuations.EffectScope +import arrow.core.continuations.Raise +import arrow.core.continuations.catch import arrow.core.continuations.effect -import arrow.core.nonFatalOrThrow import com.fraktalio.fmodel.application.Error.CommandHandlingFailed import com.fraktalio.fmodel.application.Error.CommandPublishingFailed - import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.map -context (StateComputation, StateRepository, EffectScope) +context (StateComputation, StateRepository, Raise) suspend fun C.handleWithEffect(): S = - try { - fetchState().computeNewState(this).save() - } catch (t: Throwable) { - shift(CommandHandlingFailed(this, t.nonFatalOrThrow())) + catch({ + fetchState().computeNewState(this@handleWithEffect).save() + }) { + raise(CommandHandlingFailed(this@handleWithEffect, it)) } context (StateComputation, StateRepository) fun Flow.handleWithEffect(): Flow> = - map { effect { it.handleWithEffect() } }.catch { emit(effect { shift(CommandPublishingFailed(it)) }) } + map { effect { it.handleWithEffect() } }.catch { emit(effect { raise(CommandPublishingFailed(it)) }) } + -context (StateComputation, StateLockingRepository, EffectScope) +context (StateComputation, StateLockingRepository, Raise) suspend fun C.handleOptimisticallyWithEffect(): Pair = - try { + catch({ val (state, version) = this@handleOptimisticallyWithEffect.fetchState() state .computeNewState(this@handleOptimisticallyWithEffect) .save(version) - } catch (t: Throwable) { - shift(CommandHandlingFailed(this, t.nonFatalOrThrow())) + }) { + raise(CommandHandlingFailed(this@handleOptimisticallyWithEffect, it)) } context (StateComputation, StateLockingRepository) fun Flow.handleOptimisticallyWithEffect(): Flow>> = - map { effect { it.handleOptimisticallyWithEffect() } }.catch { emit(effect { shift(CommandPublishingFailed(it)) }) } + map { effect { it.handleOptimisticallyWithEffect() } }.catch { emit(effect { raise(CommandPublishingFailed(it)) }) } -context (StateOrchestratingComputation, StateRepository, EffectScope) +context (StateOrchestratingComputation, StateRepository, Raise) @FlowPreview suspend fun C.handleWithEffect(): S = - try { - fetchState().computeNewState(this).save() - } catch (t: Throwable) { - shift(CommandHandlingFailed(this, t.nonFatalOrThrow())) + catch({ + fetchState().computeNewState(this@handleWithEffect).save() + }) { + raise(CommandHandlingFailed(this@handleWithEffect, it)) } context (StateOrchestratingComputation, StateRepository) @FlowPreview suspend fun Flow.handleWithEffect(): Flow> = - map { effect { it.handleWithEffect() } }.catch { emit(effect { shift(CommandPublishingFailed(it)) }) } + map { effect { it.handleWithEffect() } }.catch { emit(effect { raise(CommandPublishingFailed(it)) }) } -context (StateOrchestratingComputation, StateLockingRepository, EffectScope) +context (StateOrchestratingComputation, StateLockingRepository, Raise) @FlowPreview suspend fun C.handleOptimisticallyWithEffect(): Pair = - try { + catch({ val (state, version) = this@handleOptimisticallyWithEffect.fetchState() state .computeNewState(this@handleOptimisticallyWithEffect) .save(version) - } catch (t: Throwable) { - shift(CommandHandlingFailed(this, t.nonFatalOrThrow())) + }) { + raise(CommandHandlingFailed(this@handleOptimisticallyWithEffect, it)) } context (StateOrchestratingComputation, StateLockingRepository) @FlowPreview suspend fun Flow.handleOptimisticallyWithEffect(): Flow>> = - map { effect { it.handleOptimisticallyWithEffect() } }.catch { emit(effect { shift(CommandPublishingFailed(it)) }) } + map { effect { it.handleOptimisticallyWithEffect() } }.catch { emit(effect { raise(CommandPublishingFailed(it)) }) } diff --git a/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateArrowContextualTest.kt b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateArrowContextualTest.kt index f3729ab7..31937bcd 100644 --- a/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateArrowContextualTest.kt +++ b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateArrowContextualTest.kt @@ -4,6 +4,7 @@ import arrow.core.Either import arrow.core.Either.Left import arrow.core.Either.Right import arrow.core.continuations.Effect +import arrow.core.continuations.toEither import com.fraktalio.fmodel.application.Error.CommandPublishingFailed import com.fraktalio.fmodel.application.examples.numbers.even.command.EvenNumberRepository import com.fraktalio.fmodel.application.examples.numbers.even.command.evenNumberRepository diff --git a/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualTest.kt b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualTest.kt index 98752bed..2feb0ba8 100644 --- a/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualTest.kt +++ b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualTest.kt @@ -2,6 +2,7 @@ package com.fraktalio.fmodel.application import arrow.core.Either import arrow.core.continuations.Effect +import arrow.core.continuations.toEither import com.fraktalio.fmodel.application.examples.numbers.even.query.EvenNumberViewRepository import com.fraktalio.fmodel.application.examples.numbers.even.query.evenNumberViewRepository import com.fraktalio.fmodel.domain.examples.numbers.api.Description diff --git a/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualTest.kt b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualTest.kt index 7d29f279..b8da617f 100644 --- a/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualTest.kt +++ b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualTest.kt @@ -2,6 +2,7 @@ package com.fraktalio.fmodel.application import arrow.core.Either import arrow.core.continuations.Effect +import arrow.core.continuations.toEither import com.fraktalio.fmodel.application.examples.numbers.even.command.EvenNumberStateRepository import com.fraktalio.fmodel.application.examples.numbers.even.command.evenNumberStateRepository import com.fraktalio.fmodel.domain.examples.numbers.api.Description diff --git a/application-vanilla/build.gradle.kts b/application-vanilla/build.gradle.kts index c696300c..c2f8d8db 100644 --- a/application-vanilla/build.gradle.kts +++ b/application-vanilla/build.gradle.kts @@ -49,6 +49,7 @@ kotlin { arch == "aarch64" || arch.startsWith("arm") -> macosArm64() else -> macosX64() } + hostOs == "Linux" -> linuxX64() isMingwX64 -> mingwX64() else -> throw GradleException("Host OS is not supported in Kotlin/Native.") diff --git a/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/NumberSaga.kt b/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/NumberSaga.kt index 53b02417..81a4fc73 100644 --- a/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/NumberSaga.kt +++ b/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/NumberSaga.kt @@ -49,12 +49,14 @@ fun numberSaga() = Saga( NumberValue(numberEvent.value.get - 1) ) ) + is EvenNumberSubtracted -> flowOf( SubtractOddNumber( Description("${numberEvent.value.get - 1}"), NumberValue(numberEvent.value.get - 1) ) ) + else -> emptyFlow() } } @@ -87,12 +89,14 @@ fun oddNumberSaga() = Saga flowOf( SubtractEvenNumber( Description("${numberEvent.value.get + 1}"), NumberValue(numberEvent.value.get + 1) ) ) + else -> emptyFlow() } } diff --git a/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/even/command/EvenNumberDecider.kt b/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/even/command/EvenNumberDecider.kt index 4ee89d33..0f91b481 100644 --- a/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/even/command/EvenNumberDecider.kt +++ b/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/even/command/EvenNumberDecider.kt @@ -55,12 +55,14 @@ fun evenNumberDecider(): Decider flowOf( EvenNumberSubtracted( c.description, c.value ) ) + null -> emptyFlow() } }, @@ -70,10 +72,12 @@ fun evenNumberDecider(): Decider EvenNumberState( e.description, NumberValue(s.value.get - e.value.get) ) + null -> s } } diff --git a/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/even/query/EvenNumberView.kt b/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/even/query/EvenNumberView.kt index f63d2b23..d983c9be 100644 --- a/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/even/query/EvenNumberView.kt +++ b/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/even/query/EvenNumberView.kt @@ -41,10 +41,12 @@ fun evenNumberView(): View = View( Description(evenNumberState.description.get + ", " + e.description.get), NumberValue(evenNumberState.value.get + e.value.get) ) + e is EvenNumberSubtracted && (evenNumberState != null) -> EvenNumberState( Description(evenNumberState.description.get + ", " + e.description.get), NumberValue(evenNumberState.value.get - e.value.get) ) + else -> evenNumberState } } diff --git a/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/odd/command/OddNumberDecider.kt b/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/odd/command/OddNumberDecider.kt index 49e926a8..d57442dc 100644 --- a/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/odd/command/OddNumberDecider.kt +++ b/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/odd/command/OddNumberDecider.kt @@ -49,12 +49,14 @@ fun oddNumberDecider(): Decider flowOf( OddNumberSubtracted( c.description, c.value ) ) + null -> emptyFlow() } }, @@ -64,10 +66,12 @@ fun oddNumberDecider(): Decider OddNumberState( e.description, NumberValue(s.value.get - e.value.get) ) + null -> s } } diff --git a/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/odd/query/OddNumberView.kt b/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/odd/query/OddNumberView.kt index 0c889cd6..622fe8c5 100644 --- a/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/odd/query/OddNumberView.kt +++ b/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/domain/examples/numbers/odd/query/OddNumberView.kt @@ -41,10 +41,12 @@ fun oddNumberView(): View = View( Description(oddNumberState.description.get + ", " + e.description.get), NumberValue(oddNumberState.value.get + e.value.get) ) + e is OddNumberSubtracted && (oddNumberState != null) -> OddNumberState( Description(oddNumberState.description.get + ", " + e.description.get), NumberValue(oddNumberState.value.get - e.value.get) ) + else -> oddNumberState } } diff --git a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt index e0f8b028..fe6e0748 100644 --- a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt +++ b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewContextualExtension.kt @@ -68,4 +68,5 @@ fun Flow.handleOptimistically(): Flow> = map { it.handle * @receiver events of type Flow to be handled */ context (ViewStateComputation, ViewStateLockingDeduplicationRepository) -fun Flow.handleOptimisticallyWithDeduplication(eventAndVersion: Pair): Flow> = map { it.handleOptimisticallyWithDeduplication(eventAndVersion) } +fun Flow.handleOptimisticallyWithDeduplication(eventAndVersion: Pair): Flow> = + map { it.handleOptimisticallyWithDeduplication(eventAndVersion) } diff --git a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerContextualExtension.kt b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerContextualExtension.kt index aff3f1b5..f3fa3418 100644 --- a/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerContextualExtension.kt +++ b/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerContextualExtension.kt @@ -70,6 +70,7 @@ fun Flow.handleConcurrently( actorsContext, partitionKey ) + /** * Handle event / action result - Saga Manager * @receiver [SagaManager] - context receiver diff --git a/build.gradle.kts b/build.gradle.kts index 8a61c6d6..81184b50 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,6 +14,7 @@ allprojects { repositories { mavenCentral() mavenLocal() + maven("https://oss.sonatype.org/content/repositories/snapshots") } } diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index bd5f451a..96dfc26c 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -47,6 +47,7 @@ kotlin { arch == "aarch64" || arch.startsWith("arm") -> macosArm64() else -> macosX64() } + hostOs == "Linux" -> linuxX64() isMingwX64 -> mingwX64() else -> throw GradleException("Host OS is not supported in Kotlin/Native.") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7128524b..ed14f9fa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,8 +3,8 @@ coroutines = "1.6.4" dokka = "1.7.20" kotest = "5.5.1" kotest-plugin = "5.5.1" -kotlin = "1.7.20" -arrow = "1.1.3" +kotlin = "1.8.0" +arrow = "2.0.0-SNAPSHOT" [libraries] coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } From a66e031134d35a53cb86676c639fb5131c61cf58 Mon Sep 17 00:00:00 2001 From: Ivan Dugalic Date: Thu, 5 Jan 2023 13:45:37 +0100 Subject: [PATCH 11/13] Upgrading `arrow-kotlin` to 2.0.0-SNAPSHOT - toml fix --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f11dedf1..536a76cf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ dokka = "1.7.20" kotest = "5.5.4" kotest-plugin = "5.5.4" kotlin = "1.8.0" -arrow = "2.0.0-SNAPSHO +arrow = "2.0.0-SNAPSHOT" [libraries] coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } From c4c8976c912f4b7e1713d751fb8ebedc2bad879c Mon Sep 17 00:00:00 2001 From: Ivan Dugalic Date: Fri, 6 Jan 2023 22:15:12 +0100 Subject: [PATCH 12/13] Upgrading `arrow-kotlin` to 2.0.0-SNAPSHOT - to Either --- .../EventSourcingAggregateArrowExtension.kt | 68 +++++++++---------- .../MaterializedViewArrowExtension.kt | 64 ++++++++--------- .../application/SagaManagerArrowExtension.kt | 26 +++---- .../StateStoredAggregateArrowExtension.kt | 44 ++++++------ .../application/EventSourcedAggregateTest.kt | 15 ++-- .../application/MaterializedViewTest.kt | 13 ++-- .../application/StateStoredAggregateTest.kt | 20 +++--- ...urcingAggregateArrowContextualExtension.kt | 44 ++++++------ ...aterializedViewArrowContextualExtension.kt | 19 +++--- .../SagaManagerArrowContextualExtension.kt.kt | 14 ++-- ...StoredAggregateArrowContextualExtension.kt | 20 +++--- ...ventSourcedAggregateArrowContextualTest.kt | 11 +-- .../MaterializedViewArrowContextualTest.kt | 8 +++ ...StateStoredAggregateArrowContextualTest.kt | 33 ++++++++- 14 files changed, 218 insertions(+), 181 deletions(-) diff --git a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowExtension.kt b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowExtension.kt index bc231c2c..adfe2597 100644 --- a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowExtension.kt +++ b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowExtension.kt @@ -16,8 +16,8 @@ package com.fraktalio.fmodel.application -import arrow.core.continuations.Effect -import arrow.core.continuations.effect +import arrow.core.Either +import arrow.core.continuations.either import com.fraktalio.fmodel.application.Error.CommandHandlingFailed import com.fraktalio.fmodel.application.Error.CommandPublishingFailed import kotlinx.coroutines.FlowPreview @@ -27,54 +27,54 @@ import kotlinx.coroutines.flow.* * Extension function - Handles the command message of type [C] * * @param command Command message of type [C] - * @return [Flow] of [Effect] (either [Error] or Events of type [E]) + * @return [Flow] of [Either] (either [Error] or Events of type [E]) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ @FlowPreview -fun EventSourcingAggregate.handleWithEffect(command: C): Flow> = +fun EventSourcingAggregate.handleWithEffect(command: C): Flow> = command .fetchEvents() .computeNewEvents(command) .save() - .map { effect { it } } - .catch { emit(effect { raise(CommandHandlingFailed(command)) }) } + .map { either { it } } + .catch { emit(either { raise(CommandHandlingFailed(command)) }) } /** * Extension function - Handles the command message of type [C] * * @param command Command message of type [C] - * @return [Flow] of [Effect] (either [Error] or Events of type [E]) + * @return [Flow] of [Either] (either [Error] or Events of type [E]) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ @FlowPreview -fun EventSourcingOrchestratingAggregate.handleWithEffect(command: C): Flow> = +fun EventSourcingOrchestratingAggregate.handleWithEffect(command: C): Flow> = command .fetchEvents() .computeNewEventsByOrchestrating(command) { it.fetchEvents() } .save() - .map { effect { it } } - .catch { emit(effect { raise(CommandHandlingFailed(command)) }) } + .map { either { it } } + .catch { emit(either { raise(CommandHandlingFailed(command)) }) } /** * Extension function - Handles the command message of type [C] * * @param command Command message of type [C] - * @return [Flow] of [Effect] (either [Error] or Events of type [Pair]<[E], [V]>) + * @return [Flow] of [Either] (either [Error] or Events of type [Pair]<[E], [V]>) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ @FlowPreview -fun EventSourcingLockingAggregate.handleOptimisticallyWithEffect(command: C): Flow>> = +fun EventSourcingLockingAggregate.handleOptimisticallyWithEffect(command: C): Flow>> = flow { val events = command.fetchEvents() emitAll( events.map { it.first } .computeNewEvents(command) .save(events.lastOrNull()) - .map { effect> { it } } - .catch { emit(effect { raise(CommandHandlingFailed(command)) }) } + .map { either> { it } } + .catch { emit(either { raise(CommandHandlingFailed(command)) }) } ) } @@ -82,72 +82,72 @@ fun EventSourcingLockingAggregate.handleOptimisticallyW * Extension function - Handles the command message of type [C] * * @param command Command message of type [C] - * @return [Flow] of [Effect] (either [Error] or Events of type [Pair]<[E], [V]>) + * @return [Flow] of [Either] (either [Error] or Events of type [Pair]<[E], [V]>) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ @FlowPreview -fun EventSourcingLockingOrchestratingAggregate.handleOptimisticallyWithEffect(command: C): Flow>> = +fun EventSourcingLockingOrchestratingAggregate.handleOptimisticallyWithEffect(command: C): Flow>> = command .fetchEvents().map { it.first } .computeNewEventsByOrchestrating(command) { it.fetchEvents().map { pair -> pair.first } } .save(latestVersionProvider) - .map { effect> { it } } - .catch { emit(effect { raise(CommandHandlingFailed(command)) }) } + .map { either> { it } } + .catch { emit(either { raise(CommandHandlingFailed(command)) }) } @FlowPreview -fun EventSourcingAggregate.handleWithEffect(commands: Flow): Flow> = +fun EventSourcingAggregate.handleWithEffect(commands: Flow): Flow> = commands .flatMapConcat { handleWithEffect(it) } - .catch { emit(effect { raise(CommandPublishingFailed(it)) }) } + .catch { emit(either { raise(CommandPublishingFailed(it)) }) } @FlowPreview -fun EventSourcingOrchestratingAggregate.handleWithEffect(commands: Flow): Flow> = +fun EventSourcingOrchestratingAggregate.handleWithEffect(commands: Flow): Flow> = commands .flatMapConcat { handleWithEffect(it) } - .catch { emit(effect { raise(CommandPublishingFailed(it)) }) } + .catch { emit(either { raise(CommandPublishingFailed(it)) }) } @FlowPreview -fun EventSourcingLockingAggregate.handleOptimisticallyWithEffect(commands: Flow): Flow>> = +fun EventSourcingLockingAggregate.handleOptimisticallyWithEffect(commands: Flow): Flow>> = commands .flatMapConcat { handleOptimisticallyWithEffect(it) } - .catch { emit(effect { raise(CommandPublishingFailed(it)) }) } + .catch { emit(either { raise(CommandPublishingFailed(it)) }) } @FlowPreview -fun EventSourcingLockingOrchestratingAggregate.handleOptimisticallyWithEffect(commands: Flow): Flow>> = +fun EventSourcingLockingOrchestratingAggregate.handleOptimisticallyWithEffect(commands: Flow): Flow>> = commands .flatMapConcat { handleOptimisticallyWithEffect(it) } - .catch { emit(effect { raise(CommandPublishingFailed(it)) }) } + .catch { emit(either { raise(CommandPublishingFailed(it)) }) } @FlowPreview -fun C.publishWithEffect(aggregate: EventSourcingAggregate): Flow> = +fun C.publishWithEffect(aggregate: EventSourcingAggregate): Flow> = aggregate.handleWithEffect(this) @FlowPreview -fun C.publishWithEffect(aggregate: EventSourcingOrchestratingAggregate): Flow> = +fun C.publishWithEffect(aggregate: EventSourcingOrchestratingAggregate): Flow> = aggregate.handleWithEffect(this) @FlowPreview -fun C.publishOptimisticallyWithEffect(aggregate: EventSourcingLockingAggregate): Flow>> = +fun C.publishOptimisticallyWithEffect(aggregate: EventSourcingLockingAggregate): Flow>> = aggregate.handleOptimisticallyWithEffect(this) @FlowPreview -fun C.publishOptimisticallyWithEffect(aggregate: EventSourcingLockingOrchestratingAggregate): Flow>> = +fun C.publishOptimisticallyWithEffect(aggregate: EventSourcingLockingOrchestratingAggregate): Flow>> = aggregate.handleOptimisticallyWithEffect(this) @FlowPreview -fun Flow.publishWithEffect(aggregate: EventSourcingAggregate): Flow> = +fun Flow.publishWithEffect(aggregate: EventSourcingAggregate): Flow> = aggregate.handleWithEffect(this) @FlowPreview -fun Flow.publishWithEffect(aggregate: EventSourcingOrchestratingAggregate): Flow> = +fun Flow.publishWithEffect(aggregate: EventSourcingOrchestratingAggregate): Flow> = aggregate.handleWithEffect(this) @FlowPreview -fun Flow.publishOptimisticallyWithEffect(aggregate: EventSourcingLockingAggregate): Flow>> = +fun Flow.publishOptimisticallyWithEffect(aggregate: EventSourcingLockingAggregate): Flow>> = aggregate.handleOptimisticallyWithEffect(this) @FlowPreview -fun Flow.publishOptimisticallyWithEffect(aggregate: EventSourcingLockingOrchestratingAggregate): Flow>> = +fun Flow.publishOptimisticallyWithEffect(aggregate: EventSourcingLockingOrchestratingAggregate): Flow>> = aggregate.handleOptimisticallyWithEffect(this) diff --git a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowExtension.kt b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowExtension.kt index b77390d7..5793c72b 100644 --- a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowExtension.kt +++ b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowExtension.kt @@ -16,9 +16,9 @@ package com.fraktalio.fmodel.application -import arrow.core.continuations.Effect +import arrow.core.Either import arrow.core.continuations.catch -import arrow.core.continuations.effect +import arrow.core.continuations.either import com.fraktalio.fmodel.application.Error.EventHandlingFailed import com.fraktalio.fmodel.application.Error.EventPublishingFailed import kotlinx.coroutines.flow.Flow @@ -29,12 +29,12 @@ import kotlinx.coroutines.flow.map * Extension function - Handles the event of type [E] * * @param event Event of type [E] to be handled - * @return [Effect] (either [Error] or State of type [S]) + * @return [Either] (either [Error] or State of type [S]) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ -suspend fun I.handleWithEffect(event: E): Effect where I : ViewStateComputation, I : ViewStateRepository = - effect { +suspend fun I.handleWithEffect(event: E): Either where I : ViewStateComputation, I : ViewStateRepository = + either { catch({ event.fetchState().computeNewState(event).save() }) { @@ -46,12 +46,12 @@ suspend fun I.handleWithEffect(event: E): Effect where I : V * Extension function - Handles the event of type [E] * * @param event Event of type [E] to be handled - * @return [Effect] (either [Error] or State of type [Pair]<[S], [V]>) + * @return [Either] (either [Error] or State of type [Pair]<[S], [V]>) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ -suspend fun I.handleOptimisticallyWithEffect(event: E): Effect> where I : ViewStateComputation, I : ViewStateLockingRepository = - effect { +suspend fun I.handleOptimisticallyWithEffect(event: E): Either> where I : ViewStateComputation, I : ViewStateLockingRepository = + either { catch({ val (state, version) = event.fetchState() state.computeNewState(event).save(version) @@ -67,12 +67,12 @@ suspend fun I.handleOptimisticallyWithEffect(event: E): Effect to be handled - * @return [Effect] (either [Error] or State of type [Pair]<[S], [SV]>) + * @return [Either] (either [Error] or State of type [Pair]<[S], [SV]>) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ -suspend fun I.handleOptimisticallyWithDeduplicationWithEffect(eventAndVersion: Pair): Effect> where I : ViewStateComputation, I : ViewStateLockingDeduplicationRepository { - return effect { +suspend fun I.handleOptimisticallyWithDeduplicationWithEffect(eventAndVersion: Pair): Either> where I : ViewStateComputation, I : ViewStateLockingDeduplicationRepository { + return either { val (event, eventVersion) = eventAndVersion catch({ val (state, currentStateVersion) = event.fetchState() @@ -87,104 +87,104 @@ suspend fun I.handleOptimisticallyWithDeduplicationWithEffect( * Extension function - Handles the flow of events of type [E] * * @param events Flow of Events of type [E] to be handled - * @return [Flow] of [Effect] (either [Error] or State of type [S]) + * @return [Flow] of [Either] (either [Error] or State of type [S]) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ -fun I.handleWithEffect(events: Flow): Flow> where I : ViewStateComputation, I : ViewStateRepository = +fun I.handleWithEffect(events: Flow): Flow> where I : ViewStateComputation, I : ViewStateRepository = events .map { handleWithEffect(it) } - .catch { emit(effect { raise(EventPublishingFailed(it)) }) } + .catch { emit(either { raise(EventPublishingFailed(it)) }) } /** * Extension function - Handles the flow of events of type [E] * * @param events Flow of Events of type [E] to be handled - * @return [Flow] of [Effect] (either [Error] or State of type [Pair]<[S], [V]>) + * @return [Flow] of [Either] (either [Error] or State of type [Pair]<[S], [V]>) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ -fun I.handleOptimisticallyWithEffect(events: Flow): Flow>> where I : ViewStateComputation, I : ViewStateLockingRepository = +fun I.handleOptimisticallyWithEffect(events: Flow): Flow>> where I : ViewStateComputation, I : ViewStateLockingRepository = events .map { handleOptimisticallyWithEffect(it) } - .catch { emit(effect { raise(EventPublishingFailed(it)) }) } + .catch { emit(either { raise(EventPublishingFailed(it)) }) } /** * Extension function - Handles the flow of events of type [Pair]<[E], [EV]> * * @param eventsAndVersions Flow of Events of type [E] to be handled - * @return [Flow] of [Effect] (either [Error] or State of type [Pair]<[S], [SV]>) + * @return [Flow] of [Either] (either [Error] or State of type [Pair]<[S], [SV]>) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ -fun I.handleOptimisticallyWithDeduplicationWithEffect(eventsAndVersions: Flow>): Flow>> where I : ViewStateComputation, I : ViewStateLockingDeduplicationRepository = +fun I.handleOptimisticallyWithDeduplicationWithEffect(eventsAndVersions: Flow>): Flow>> where I : ViewStateComputation, I : ViewStateLockingDeduplicationRepository = eventsAndVersions .map { handleOptimisticallyWithDeduplicationWithEffect(it) } - .catch { emit(effect { raise(EventPublishingFailed(it)) }) } + .catch { emit(either { raise(EventPublishingFailed(it)) }) } /** * Extension function - Publishes the event of type [E] to the materialized view * @receiver event of type [E] * @param materializedView of type [ViewStateComputation]<[S], [E]>, [ViewStateRepository]<[E], [S]> - * @return [Effect] (either [Error] or the successfully stored State of type [S]) + * @return [Either] (either [Error] or the successfully stored State of type [S]) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ -suspend fun E.publishWithEffect(materializedView: M): Effect where M : ViewStateComputation, M : ViewStateRepository = +suspend fun E.publishWithEffect(materializedView: M): Either where M : ViewStateComputation, M : ViewStateRepository = materializedView.handleWithEffect(this) /** * Extension function - Publishes the event of type [E] to the materialized view * @receiver event of type [E] * @param materializedView of type [ViewStateComputation]<[S], [E]>, [ViewStateLockingRepository]<[E], [S], [V]> - * @return [Effect] (either [Error] or the successfully stored State of type [Pair]<[S], [V]>) + * @return [Either] (either [Error] or the successfully stored State of type [Pair]<[S], [V]>) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ -suspend fun E.publishOptimisticallyWithEffect(materializedView: M): Effect> where M : ViewStateComputation, M : ViewStateLockingRepository = +suspend fun E.publishOptimisticallyWithEffect(materializedView: M): Either> where M : ViewStateComputation, M : ViewStateLockingRepository = materializedView.handleOptimisticallyWithEffect(this) /** * Extension function - Publishes the event of type [E] to the materialized view * @receiver event of type [E] * @param materializedView of type [ViewStateComputation]<[S], [E]>, [ViewStateLockingDeduplicationRepository]<[E], [S], [EV], [SV]> - * @return [Effect] (either [Error] or the successfully stored State of type [Pair]<[S], [SV]>) + * @return [Either] (either [Error] or the successfully stored State of type [Pair]<[S], [SV]>) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ -suspend fun Pair.publishOptimisticallyWithDeduplicationWithEffect(materializedView: M): Effect> where M : ViewStateComputation, M : ViewStateLockingDeduplicationRepository = +suspend fun Pair.publishOptimisticallyWithDeduplicationWithEffect(materializedView: M): Either> where M : ViewStateComputation, M : ViewStateLockingDeduplicationRepository = materializedView.handleOptimisticallyWithDeduplicationWithEffect(this) /** * Extension function - Publishes the event of type [E] to the materialized view * @receiver [Flow] of events of type [E] * @param materializedView of type [ViewStateComputation]<[S], [E]>, [ViewStateRepository]<[E], [S]> - * @return [Flow] of [Effect] (either [Error] or the successfully stored State of type [S]) + * @return [Flow] of [Either] (either [Error] or the successfully stored State of type [S]) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ -fun Flow.publishWithEffect(materializedView: M): Flow> where M : ViewStateComputation, M : ViewStateRepository = +fun Flow.publishWithEffect(materializedView: M): Flow> where M : ViewStateComputation, M : ViewStateRepository = materializedView.handleWithEffect(this) /** * Extension function - Publishes the event of type [E] to the materialized view * @receiver [Flow] of events of type [E] * @param materializedView of type [ViewStateComputation]<[S], [E]>, [ViewStateLockingRepository]<[E], [S], [V]> - * @return [Flow] of [Effect] (either [Error] or the successfully stored State of type [Pair]<[S], [V]>) + * @return [Flow] of [Either] (either [Error] or the successfully stored State of type [Pair]<[S], [V]>) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ -fun Flow.publishOptimisticallyWithEffect(materializedView: M): Flow>> where M : ViewStateComputation, M : ViewStateLockingRepository = +fun Flow.publishOptimisticallyWithEffect(materializedView: M): Flow>> where M : ViewStateComputation, M : ViewStateLockingRepository = materializedView.handleOptimisticallyWithEffect(this) /** * Extension function - Publishes the event of type [E] to the materialized view * @receiver [Flow] of events of type [E] * @param materializedView of type [ViewStateComputation]<[S], [E]>, [ViewStateLockingDeduplicationRepository]<[E], [S], [EV], [SV]> - * @return [Flow] of [Effect] (either [Error] or the successfully stored State of type [Pair]<[S], [SV]>) + * @return [Flow] of [Either] (either [Error] or the successfully stored State of type [Pair]<[S], [SV]>) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ -fun Flow>.publishOptimisticallyWithDeduplicationWithEffect(materializedView: M): Flow>> where M : ViewStateComputation, M : ViewStateLockingDeduplicationRepository = +fun Flow>.publishOptimisticallyWithDeduplicationWithEffect(materializedView: M): Flow>> where M : ViewStateComputation, M : ViewStateLockingDeduplicationRepository = materializedView.handleOptimisticallyWithDeduplicationWithEffect(this) diff --git a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowExtension.kt b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowExtension.kt index ab832ebc..55f8357e 100644 --- a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowExtension.kt +++ b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowExtension.kt @@ -16,8 +16,8 @@ package com.fraktalio.fmodel.application -import arrow.core.continuations.Effect -import arrow.core.continuations.effect +import arrow.core.Either +import arrow.core.continuations.either import com.fraktalio.fmodel.application.Error.ActionResultHandlingFailed import com.fraktalio.fmodel.application.Error.ActionResultPublishingFailed import kotlinx.coroutines.FlowPreview @@ -30,52 +30,52 @@ import kotlinx.coroutines.flow.map * Extension function - Handles the action result of type [AR]. * * @param actionResult Action Result represent the outcome of some action you want to handle in some way - * @return [Flow] of [Effect] (either [Error] or Actions of type [A]) + * @return [Flow] of [Either] (either [Error] or Actions of type [A]) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ -fun SagaManager.handleWithEffect(actionResult: AR): Flow> = +fun SagaManager.handleWithEffect(actionResult: AR): Flow> = actionResult .computeNewActions() .publish() - .map { effect { it } } - .catch { emit(effect { raise(ActionResultHandlingFailed(actionResult, it)) }) } + .map { either { it } } + .catch { emit(either { raise(ActionResultHandlingFailed(actionResult, it)) }) } /** * Extension function - Handles the [Flow] of action results of type [AR]. * * @param actionResults Action Results represent the outcome of some action you want to handle in some way - * @return [Flow] of [Effect] (either [Error] or Actions of type [A]) + * @return [Flow] of [Either] (either [Error] or Actions of type [A]) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ @FlowPreview -fun SagaManager.handleWithEffect(actionResults: Flow): Flow> = +fun SagaManager.handleWithEffect(actionResults: Flow): Flow> = actionResults .flatMapConcat { handleWithEffect(it) } - .catch { emit(effect { raise(ActionResultPublishingFailed(it)) }) } + .catch { emit(either { raise(ActionResultPublishingFailed(it)) }) } /** * Extension function - Publishes the action result of type [AR] to the saga manager of type [SagaManager]<[AR], [A]> * @receiver action result of type [AR] * @param sagaManager of type [SagaManager]<[AR], [A]> - * @return the [Flow] of [Effect] (either [Error] or successfully published Actions of type [A]) + * @return the [Flow] of [Either] (either [Error] or successfully published Actions of type [A]) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ -fun AR.publishWithEffect(sagaManager: SagaManager): Flow> = +fun AR.publishWithEffect(sagaManager: SagaManager): Flow> = sagaManager.handleWithEffect(this) /** * Extension function - Publishes the action result of type [AR] to the saga manager of type [SagaManager]<[AR], [A]> * @receiver [Flow] of action results of type [AR] * @param sagaManager of type [SagaManager]<[AR], [A]> - * @return the [Flow] of [Effect] (either [Error] or successfully published Actions of type [A]) + * @return the [Flow] of [Either] (either [Error] or successfully published Actions of type [A]) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ @FlowPreview -fun Flow.publishWithEffect(sagaManager: SagaManager): Flow> = +fun Flow.publishWithEffect(sagaManager: SagaManager): Flow> = sagaManager.handleWithEffect(this) diff --git a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowExtension.kt b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowExtension.kt index ef89be55..839856ec 100644 --- a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowExtension.kt +++ b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowExtension.kt @@ -16,9 +16,9 @@ package com.fraktalio.fmodel.application -import arrow.core.continuations.Effect +import arrow.core.Either import arrow.core.continuations.catch -import arrow.core.continuations.effect +import arrow.core.continuations.either import com.fraktalio.fmodel.application.Error.CommandHandlingFailed import com.fraktalio.fmodel.application.Error.CommandPublishingFailed import kotlinx.coroutines.FlowPreview @@ -30,14 +30,14 @@ import kotlinx.coroutines.flow.map * Extension function - Handles the command message of type [C] * * @param command Command message of type [C] - * @return [Effect] (either [Error] or State of type [S]) + * @return [Either] (either [Error] or State of type [S]) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ @FlowPreview -suspend fun I.handleWithEffect(command: C): Effect where I : StateComputation, +suspend fun I.handleWithEffect(command: C): Either where I : StateComputation, I : StateRepository = - effect { + either { catch({ command.fetchState().computeNewState(command).save() }) { @@ -50,14 +50,14 @@ suspend fun I.handleWithEffect(command: C): Effect where * Extension function - Handles the command message of type [C] to the locking state stored aggregate, optimistically * * @param command Command message of type [C] - * @return [Effect] (either [Error] or State of type [Pair]<[S], [V]>), in which [V] represents Version + * @return [Either] (either [Error] or State of type [Pair]<[S], [V]>), in which [V] represents Version * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ @FlowPreview -suspend fun I.handleOptimisticallyWithEffect(command: C): Effect> where I : StateComputation, +suspend fun I.handleOptimisticallyWithEffect(command: C): Either> where I : StateComputation, I : StateLockingRepository = - effect { + either { catch({ val (state, version) = command.fetchState() state @@ -72,42 +72,42 @@ suspend fun I.handleOptimisticallyWithEffect(command: C): Effect * Extension function - Handles the [Flow] of command messages of type [C] * * @param commands [Flow] of Command messages of type [C] - * @return [Flow] of [Effect] (either [Error] or State of type [S]) + * @return [Flow] of [Either] (either [Error] or State of type [S]) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ @FlowPreview -fun I.handleWithEffect(commands: Flow): Flow> where I : StateComputation, +fun I.handleWithEffect(commands: Flow): Flow> where I : StateComputation, I : StateRepository = commands .map { handleWithEffect(it) } - .catch { emit(effect { raise(CommandPublishingFailed(it)) }) } + .catch { emit(either { raise(CommandPublishingFailed(it)) }) } /** * Extension function - Handles the [Flow] of command messages of type [C] to the locking state stored aggregate, optimistically * * @param commands [Flow] of Command messages of type [C] - * @return [Flow] of [Effect] (either [Error] or State of type [Pair]<[S], [V]>) + * @return [Flow] of [Either] (either [Error] or State of type [Pair]<[S], [V]>) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ @FlowPreview -fun I.handleOptimisticallyWithEffect(commands: Flow): Flow>> where I : StateComputation, +fun I.handleOptimisticallyWithEffect(commands: Flow): Flow>> where I : StateComputation, I : StateLockingRepository = commands .map { handleOptimisticallyWithEffect(it) } - .catch { emit(effect { raise(CommandPublishingFailed(it)) }) } + .catch { emit(either { raise(CommandPublishingFailed(it)) }) } /** * Extension function - Publishes the command of type [C] to the state stored aggregate * @receiver command of type [C] * @param aggregate of type [StateComputation]<[C], [S], [E]>, [StateRepository]<[C], [S]> - * @return [Effect] (either [Error] or successfully stored State of type [S]) + * @return [Either] (either [Error] or successfully stored State of type [S]) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ @FlowPreview -suspend fun C.publishWithEffect(aggregate: A): Effect where A : StateComputation, +suspend fun C.publishWithEffect(aggregate: A): Either where A : StateComputation, A : StateRepository = aggregate.handleWithEffect(this) @@ -115,12 +115,12 @@ suspend fun C.publishWithEffect(aggregate: A): Effect whe * Extension function - Publishes the command of type [C] to the locking state stored aggregate, optimistically * @receiver command of type [C] * @param aggregate of type [StateComputation]<[C], [S], [E]>, [StateLockingRepository]<[C], [S], [V]> - * @return [Effect] (either [Error] or successfully stored State of type [Pair]<[S], [V]>) + * @return [Either] (either [Error] or successfully stored State of type [Pair]<[S], [V]>) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ @FlowPreview -suspend fun C.publishOptimisticallyWithEffect(aggregate: A): Effect> where A : StateComputation, +suspend fun C.publishOptimisticallyWithEffect(aggregate: A): Either> where A : StateComputation, A : StateLockingRepository = aggregate.handleOptimisticallyWithEffect(this) @@ -128,12 +128,12 @@ suspend fun C.publishOptimisticallyWithEffect(aggregate: A): Eff * Extension function - Publishes the command of type [C] to the state stored aggregate * @receiver [Flow] of commands of type [C] * @param aggregate of type [StateComputation]<[C], [S], [E]>, [StateRepository]<[C], [S]> - * @return the [Flow] of [Effect] (either [Error] or successfully stored State of type [S]) + * @return the [Flow] of [Either] (either [Error] or successfully stored State of type [S]) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ @FlowPreview -fun Flow.publishWithEffect(aggregate: A): Flow> where A : StateComputation, +fun Flow.publishWithEffect(aggregate: A): Flow> where A : StateComputation, A : StateRepository = aggregate.handleWithEffect(this) @@ -141,11 +141,11 @@ fun Flow.publishWithEffect(aggregate: A): Flow> * Extension function - Publishes the command of type [C] to the locking state stored aggregate, optimistically * @receiver [Flow] of commands of type [C] * @param aggregate of type [StateComputation]<[C], [S], [E]>, [StateLockingRepository]<[C], [S], [V]> - * @return the [Flow] of [Effect] (either [Error] or successfully stored State of type [Pair]<[S], [V]>) + * @return the [Flow] of [Either] (either [Error] or successfully stored State of type [Pair]<[S], [V]>) * * @author Иван Дугалић / Ivan Dugalic / @idugalic */ @FlowPreview -fun Flow.publishOptimisticallyWithEffect(aggregate: A): Flow>> where A : StateComputation, +fun Flow.publishOptimisticallyWithEffect(aggregate: A): Flow>> where A : StateComputation, A : StateLockingRepository = aggregate.handleOptimisticallyWithEffect(this) diff --git a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateTest.kt b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateTest.kt index 03b53ccb..45d9c98f 100644 --- a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateTest.kt +++ b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateTest.kt @@ -3,8 +3,6 @@ package com.fraktalio.fmodel.application import arrow.core.Either import arrow.core.Either.Left import arrow.core.Either.Right -import arrow.core.continuations.Effect -import arrow.core.continuations.toEither import com.fraktalio.fmodel.application.examples.numbers.NumberRepository import com.fraktalio.fmodel.application.examples.numbers.even.command.EvenNumberLockingRepository import com.fraktalio.fmodel.application.examples.numbers.even.command.EvenNumberRepository @@ -23,7 +21,6 @@ import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.collections.shouldContainExactly import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toList /** @@ -33,7 +30,7 @@ import kotlinx.coroutines.flow.toList private fun IDecider.given( repository: EventRepository, command: () -> C -): Flow> = +): Flow> = eventSourcingAggregate( decider = this, eventRepository = repository @@ -43,7 +40,7 @@ private fun IDecider.given( private fun IDecider.given( repository: EventLockingRepository, command: () -> C -): Flow>> = +): Flow>> = eventSourcingLockingAggregate( decider = this, eventRepository = repository @@ -58,11 +55,11 @@ private fun IDecider.whenCommand(command: C): C = command /** * DSL - Then */ -private suspend infix fun Flow>.thenEvents(expected: Iterable>) = - map { it.toEither() }.toList() shouldContainExactly (expected) +private suspend infix fun Flow>.thenEvents(expected: Iterable>) = + toList() shouldContainExactly (expected) -private suspend infix fun Flow>>.thenEventPairs(expected: Iterable>>) = - map { it.toEither() }.toList() shouldContainExactly (expected) +private suspend infix fun Flow>>.thenEventPairs(expected: Iterable>>) = + toList() shouldContainExactly (expected) /** * Event sourced aggregate test diff --git a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewTest.kt b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewTest.kt index e4d37d43..0acaab89 100644 --- a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewTest.kt +++ b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewTest.kt @@ -26,7 +26,7 @@ import io.kotest.matchers.types.shouldBeInstanceOf /** * DSL - Given */ -private suspend fun IView.given(repository: ViewStateRepository, event: () -> E): Effect = +private suspend fun IView.given(repository: ViewStateRepository, event: () -> E): Either = materializedView( view = this, viewStateRepository = repository @@ -35,12 +35,13 @@ private suspend fun IView.given(repository: ViewStateRepository IView.given( repository: ViewStateLockingRepository, event: () -> E -): Effect> = +): Either> = materializedLockingView( view = this, viewStateRepository = repository ).handleOptimisticallyWithEffect(event()) + /** * DSL - When */ @@ -50,16 +51,16 @@ private fun IView.whenEvent(event: E): E = event /** * DSL - Then */ -private suspend infix fun Effect.thenState(expected: S) { - val state = when (val result = this.toEither()) { +private infix fun Either.thenState(expected: S) { + val state = when (val result = this) { is Either.Right -> result.value is Either.Left -> throw AssertionError("Expected Either.Right, but found Either.Left with value ${result.value}") } return state shouldBe expected } -private suspend infix fun Effect>.thenStateAndVersion(expected: Pair) { - val state = when (val result = this.toEither()) { +private infix fun Either>.thenStateAndVersion(expected: Pair) { + val state = when (val result = this) { is Either.Right -> result.value is Either.Left -> throw AssertionError("Expected Either.Right, but found Either.Left with value ${result.value}") } diff --git a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateTest.kt b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateTest.kt index b4584c6b..37e722b9 100644 --- a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateTest.kt +++ b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateTest.kt @@ -1,8 +1,6 @@ package com.fraktalio.fmodel.application import arrow.core.Either -import arrow.core.continuations.Effect -import arrow.core.continuations.toEither import com.fraktalio.fmodel.application.examples.numbers.NumberStateRepository import com.fraktalio.fmodel.application.examples.numbers.even.command.EvenNumberLockingStateRepository import com.fraktalio.fmodel.application.examples.numbers.even.command.EvenNumberStateRepository @@ -30,7 +28,7 @@ import kotlinx.coroutines.FlowPreview private suspend fun IDecider.given( repository: StateRepository, command: () -> C -): Effect = +): Either = stateStoredAggregate( decider = this, stateRepository = repository @@ -40,7 +38,7 @@ private suspend fun IDecider.given( private suspend fun IDecider.given( repository: StateLockingRepository, command: () -> C -): Effect> = +): Either> = stateStoredLockingAggregate( decider = this, stateRepository = repository @@ -55,16 +53,16 @@ private fun IDecider.whenCommand(command: C): C = command /** * DSL - Then */ -private suspend infix fun Effect.thenState(expected: S) { - val state = when (val result = this.toEither()) { +private infix fun Either.thenState(expected: S) { + val state = when (val result = this) { is Either.Right -> result.value is Either.Left -> throw AssertionError("Expected Either.Right, but found Either.Left with value ${result.value}") } return state shouldBe expected } -private suspend infix fun Effect>.thenStateAndVersion(expected: Pair) { - val state = when (val result = this.toEither()) { +private infix fun Either>.thenStateAndVersion(expected: Pair) { + val state = when (val result = this) { is Either.Right -> result.value is Either.Left -> throw AssertionError("Expected Either.Right, but found Either.Left with value ${result.value}") } @@ -72,8 +70,8 @@ private suspend infix fun Effect>.thenStateAndVersion(e } -private suspend fun Effect.thenError() { - val error = when (val result = this.toEither()) { +private fun Either.thenError() { + val error = when (val result = this) { is Either.Right -> throw AssertionError("Expected Either.Left, but found Either.Right with value ${result.value}") is Either.Left -> result.value } @@ -120,9 +118,7 @@ class StateStoredAggregateTest : FunSpec({ given(evenNumberStateRepository) { whenCommand(command) }.thenError() - } - } test("Combined State-stored aggregate - add even number") { diff --git a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowContextualExtension.kt b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowContextualExtension.kt index a36a6431..0e23e16b 100644 --- a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowContextualExtension.kt +++ b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowContextualExtension.kt @@ -1,7 +1,7 @@ package com.fraktalio.fmodel.application -import arrow.core.continuations.Effect -import arrow.core.continuations.effect +import arrow.core.Either +import arrow.core.continuations.either import com.fraktalio.fmodel.application.Error.CommandHandlingFailed import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.* @@ -9,60 +9,60 @@ import kotlinx.coroutines.flow.* context (EventComputation, EventRepository) @FlowPreview -fun C.handleWithEffect(): Flow> = +fun C.handleWithEffect(): Flow> = fetchEvents() .computeNewEvents(this) .save() - .map { effect { it } } - .catch { emit(effect { raise(CommandHandlingFailed(this@handleWithEffect)) }) } + .map { either { it } } + .catch { emit(either { raise(CommandHandlingFailed(this@handleWithEffect)) }) } context (EventComputation, EventLockingRepository) @FlowPreview -fun C.handleOptimisticallyWithEffect(): Flow>> = flow { +fun C.handleOptimisticallyWithEffect(): Flow>> = flow { val events = this@handleOptimisticallyWithEffect.fetchEvents() emitAll( events.map { it.first } .computeNewEvents(this@handleOptimisticallyWithEffect) .save(events.lastOrNull()) - .map { effect> { it } } - .catch { emit(effect { raise(CommandHandlingFailed(this@handleOptimisticallyWithEffect)) }) } + .map { either> { it } } + .catch { emit(either { raise(CommandHandlingFailed(this@handleOptimisticallyWithEffect)) }) } ) } context (EventOrchestratingComputation, EventRepository) @FlowPreview -fun C.handleWithEffect(): Flow> = +fun C.handleWithEffect(): Flow> = fetchEvents() .computeNewEventsByOrchestrating(this) { it.fetchEvents() } .save() - .map { effect { it } } - .catch { emit(effect { raise(CommandHandlingFailed(this@handleWithEffect)) }) } + .map { either { it } } + .catch { emit(either { raise(CommandHandlingFailed(this@handleWithEffect)) }) } context (EventOrchestratingComputation, EventLockingRepository) @FlowPreview -fun C.handleOptimisticallyWithEffect(): Flow>> = +fun C.handleOptimisticallyWithEffect(): Flow>> = fetchEvents().map { it.first } .computeNewEventsByOrchestrating(this) { it.fetchEvents().map { pair -> pair.first } } .save(latestVersionProvider) - .map { effect> { it } } - .catch { emit(effect { raise(CommandHandlingFailed(this@handleOptimisticallyWithEffect)) }) } + .map { either> { it } } + .catch { emit(either { raise(CommandHandlingFailed(this@handleOptimisticallyWithEffect)) }) } context (EventComputation, EventRepository) @FlowPreview -fun Flow.handleWithEffect(): Flow> = - flatMapConcat { it.handleWithEffect() }.catch { emit(effect { raise(Error.CommandPublishingFailed(it)) }) } +fun Flow.handleWithEffect(): Flow> = + flatMapConcat { it.handleWithEffect() }.catch { emit(either { raise(Error.CommandPublishingFailed(it)) }) } context (EventComputation, EventLockingRepository) @FlowPreview -fun Flow.handleOptimisticallyWithEffect(): Flow>> = - flatMapConcat { it.handleOptimisticallyWithEffect() }.catch { emit(effect { raise(Error.CommandPublishingFailed(it)) }) } +fun Flow.handleOptimisticallyWithEffect(): Flow>> = + flatMapConcat { it.handleOptimisticallyWithEffect() }.catch { emit(either { raise(Error.CommandPublishingFailed(it)) }) } context (EventOrchestratingComputation, EventRepository) @FlowPreview -fun Flow.handleWithEffect(): Flow> = - flatMapConcat { it.handleWithEffect() }.catch { emit(effect { raise(Error.CommandPublishingFailed(it)) }) } +fun Flow.handleWithEffect(): Flow> = + flatMapConcat { it.handleWithEffect() }.catch { emit(either { raise(Error.CommandPublishingFailed(it)) }) } context (EventOrchestratingComputation, EventLockingRepository) @FlowPreview -fun Flow.handleOptimisticallyWithEffect(): Flow>> = - flatMapConcat { it.handleOptimisticallyWithEffect() }.catch { emit(effect { raise(Error.CommandPublishingFailed(it)) }) } +fun Flow.handleOptimisticallyWithEffect(): Flow>> = + flatMapConcat { it.handleOptimisticallyWithEffect() }.catch { emit(either { raise(Error.CommandPublishingFailed(it)) }) } diff --git a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualExtension.kt b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualExtension.kt index 09ea7a62..86930e62 100644 --- a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualExtension.kt +++ b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualExtension.kt @@ -1,6 +1,9 @@ package com.fraktalio.fmodel.application -import arrow.core.continuations.* +import arrow.core.Either +import arrow.core.continuations.Raise +import arrow.core.continuations.catch +import arrow.core.continuations.either import com.fraktalio.fmodel.application.Error.EventHandlingFailed import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch @@ -34,14 +37,14 @@ suspend fun E.handleOptimisticallyWithDeduplicationAndEffect(even } context (ViewStateComputation, ViewStateRepository) -fun Flow.handleWithEffect(): Flow> = - map { effect { it.handleWithEffect() } }.catch { emit(effect { raise(Error.EventPublishingFailed(it)) }) } +fun Flow.handleWithEffect(): Flow> = + map { either { it.handleWithEffect() } }.catch { emit(either { raise(Error.EventPublishingFailed(it)) }) } context (ViewStateComputation, ViewStateLockingRepository) -fun Flow.handleOptimisticallyWithEffect(): Flow>> = - map { effect { it.handleOptimisticallyWithEffect() } }.catch { emit(effect { raise(Error.EventPublishingFailed(it)) }) } +fun Flow.handleOptimisticallyWithEffect(): Flow>> = + map { either { it.handleOptimisticallyWithEffect() } }.catch { emit(either { raise(Error.EventPublishingFailed(it)) }) } context (ViewStateComputation, ViewStateLockingDeduplicationRepository) -fun Flow.handleOptimisticallyWithDeduplicationAndEffect(eventAndVersion: Pair): Flow>> = - map { effect { it.handleOptimisticallyWithDeduplicationAndEffect(eventAndVersion) } } - .catch { emit(effect { raise(Error.EventPublishingFailed(it)) }) } +fun Flow.handleOptimisticallyWithDeduplicationAndEffect(eventAndVersion: Pair): Flow>> = + map { either { it.handleOptimisticallyWithDeduplicationAndEffect(eventAndVersion) } } + .catch { emit(either { raise(Error.EventPublishingFailed(it)) }) } diff --git a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowContextualExtension.kt.kt b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowContextualExtension.kt.kt index f83d7ff6..c593cbe4 100644 --- a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowContextualExtension.kt.kt +++ b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowContextualExtension.kt.kt @@ -1,7 +1,7 @@ package com.fraktalio.fmodel.application -import arrow.core.continuations.Effect -import arrow.core.continuations.effect +import arrow.core.Either +import arrow.core.continuations.either import com.fraktalio.fmodel.application.Error.ActionResultHandlingFailed import com.fraktalio.fmodel.application.Error.ActionResultPublishingFailed import kotlinx.coroutines.FlowPreview @@ -11,13 +11,13 @@ import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.map context (SagaManager) -fun AR.handleWithEffect(): Flow> = +fun AR.handleWithEffect(): Flow> = computeNewActions() .publish() - .map { effect { it } } - .catch { emit(effect { raise(ActionResultHandlingFailed(this@handleWithEffect, it)) }) } + .map { either { it } } + .catch { emit(either { raise(ActionResultHandlingFailed(this@handleWithEffect, it)) }) } context (SagaManager) @FlowPreview -fun Flow.handleWithEffect(): Flow> = - flatMapConcat { it.handleWithEffect() }.catch { emit(effect { raise(ActionResultPublishingFailed(it)) }) } +fun Flow.handleWithEffect(): Flow> = + flatMapConcat { it.handleWithEffect() }.catch { emit(either { raise(ActionResultPublishingFailed(it)) }) } diff --git a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualExtension.kt b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualExtension.kt index 16785870..b7d0c8f7 100644 --- a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualExtension.kt +++ b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualExtension.kt @@ -1,9 +1,9 @@ package com.fraktalio.fmodel.application -import arrow.core.continuations.Effect +import arrow.core.Either import arrow.core.continuations.Raise import arrow.core.continuations.catch -import arrow.core.continuations.effect +import arrow.core.continuations.either import com.fraktalio.fmodel.application.Error.CommandHandlingFailed import com.fraktalio.fmodel.application.Error.CommandPublishingFailed import kotlinx.coroutines.FlowPreview @@ -20,8 +20,8 @@ suspend fun C.handleWithEffect(): S = } context (StateComputation, StateRepository) -fun Flow.handleWithEffect(): Flow> = - map { effect { it.handleWithEffect() } }.catch { emit(effect { raise(CommandPublishingFailed(it)) }) } +fun Flow.handleWithEffect(): Flow> = + map { either { it.handleWithEffect() } }.catch { emit(either { raise(CommandPublishingFailed(it)) }) } context (StateComputation, StateLockingRepository, Raise) @@ -36,8 +36,8 @@ suspend fun C.handleOptimisticallyWithEffect(): Pair = } context (StateComputation, StateLockingRepository) -fun Flow.handleOptimisticallyWithEffect(): Flow>> = - map { effect { it.handleOptimisticallyWithEffect() } }.catch { emit(effect { raise(CommandPublishingFailed(it)) }) } +fun Flow.handleOptimisticallyWithEffect(): Flow>> = + map { either { it.handleOptimisticallyWithEffect() } }.catch { emit(either { raise(CommandPublishingFailed(it)) }) } context (StateOrchestratingComputation, StateRepository, Raise) @FlowPreview @@ -50,8 +50,8 @@ suspend fun C.handleWithEffect(): S = context (StateOrchestratingComputation, StateRepository) @FlowPreview -suspend fun Flow.handleWithEffect(): Flow> = - map { effect { it.handleWithEffect() } }.catch { emit(effect { raise(CommandPublishingFailed(it)) }) } +suspend fun Flow.handleWithEffect(): Flow> = + map { either { it.handleWithEffect() } }.catch { emit(either { raise(CommandPublishingFailed(it)) }) } context (StateOrchestratingComputation, StateLockingRepository, Raise) @@ -68,5 +68,5 @@ suspend fun C.handleOptimisticallyWithEffect(): Pair = context (StateOrchestratingComputation, StateLockingRepository) @FlowPreview -suspend fun Flow.handleOptimisticallyWithEffect(): Flow>> = - map { effect { it.handleOptimisticallyWithEffect() } }.catch { emit(effect { raise(CommandPublishingFailed(it)) }) } +suspend fun Flow.handleOptimisticallyWithEffect(): Flow>> = + map { either { it.handleOptimisticallyWithEffect() } }.catch { emit(either { raise(CommandPublishingFailed(it)) }) } diff --git a/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateArrowContextualTest.kt b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateArrowContextualTest.kt index 31937bcd..d2bf640c 100644 --- a/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateArrowContextualTest.kt +++ b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/EventSourcedAggregateArrowContextualTest.kt @@ -3,8 +3,6 @@ package com.fraktalio.fmodel.application import arrow.core.Either import arrow.core.Either.Left import arrow.core.Either.Right -import arrow.core.continuations.Effect -import arrow.core.continuations.toEither import com.fraktalio.fmodel.application.Error.CommandPublishingFailed import com.fraktalio.fmodel.application.examples.numbers.even.command.EvenNumberRepository import com.fraktalio.fmodel.application.examples.numbers.even.command.evenNumberRepository @@ -17,11 +15,14 @@ import com.fraktalio.fmodel.domain.examples.numbers.even.command.evenNumberDecid import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.collections.shouldContainExactly import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.toList import kotlin.contracts.ExperimentalContracts -private suspend infix fun Flow>.thenEvents(expected: Iterable>) = - map { it.toEither() }.toList() shouldContainExactly (expected) +private suspend infix fun Flow>.thenEvents(expected: Iterable>) = + toList() shouldContainExactly (expected) /** * Event sourced aggregate contextual (context receivers) test diff --git a/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualTest.kt b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualTest.kt index 2feb0ba8..be7ecbb0 100644 --- a/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualTest.kt +++ b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualTest.kt @@ -25,6 +25,14 @@ private suspend infix fun Effect.thenState(expected: S) { return state shouldBe expected } +private suspend infix fun Either.thenState(expected: S) { + val state = when (val result = this) { + is Either.Right -> result.value + is Either.Left -> throw AssertionError("Expected Either.Right, but found Either.Left with value ${result.value}") + } + return state shouldBe expected +} + /** * Materialized View Contextual Test */ diff --git a/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualTest.kt b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualTest.kt index b8da617f..f512777f 100644 --- a/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualTest.kt +++ b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualTest.kt @@ -16,6 +16,7 @@ import io.kotest.matchers.types.shouldBeInstanceOf import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.last import kotlin.contracts.ExperimentalContracts private suspend fun Effect.thenError() { @@ -26,6 +27,14 @@ private suspend fun Effect.thenError() { error.shouldBeInstanceOf() } +private fun Either.thenError() { + val error = when (val result = this) { + is Either.Right -> throw AssertionError("Expected Either.Left, but found Either.Right with value ${result.value}") + is Either.Left -> result.value + } + error.shouldBeInstanceOf() +} + private suspend infix fun Effect.thenState(expected: S) { val state = when (val result = this.toEither()) { is Either.Right -> result.value @@ -34,6 +43,14 @@ private suspend infix fun Effect.thenState(expected: S) { return state shouldBe expected } +private infix fun Either.thenState(expected: S) { + val state = when (val result = this) { + is Either.Right -> result.value + is Either.Left -> throw AssertionError("Expected Either.Right, but found Either.Left with value ${result.value}") + } + return state shouldBe expected +} + /** * State-stored aggregate arrow, contextual test */ @@ -64,12 +81,26 @@ class StateStoredAggregateArrowContextualTest : FunSpec({ } test("State-stored aggregate arrow contextual - add even number - exception (large number > 1000)") { + evenNumberStateRepository.deleteAll() with(stateComputation(evenDecider)) { with(evenNumberStateRepository) { flowOf( - AddEvenNumber(Description("desc"), NumberValue(6000)) + AddEvenNumber(Description("desc"), NumberValue(6000)), + AddEvenNumber(Description("desc"), NumberValue(6)) ).handleWithEffect().first().thenError() } } } + + test("State-stored aggregate arrow contextual - add even number - exception (large number > 1000) - 2") { + evenNumberStateRepository.deleteAll() + with(stateComputation(evenDecider)) { + with(evenNumberStateRepository) { + flowOf( + AddEvenNumber(Description("desc"), NumberValue(6000)), + AddEvenNumber(Description("desc"), NumberValue(6)) + ).handleWithEffect().last() thenState EvenNumberState(Description("desc"), NumberValue(6)) + } + } + } }) From bda40cfebebc42723d61d3aef4fa599d57c586df Mon Sep 17 00:00:00 2001 From: Ivan Dugalic Date: Sat, 15 Apr 2023 13:30:19 +0200 Subject: [PATCH 13/13] arrow 2 snapshot upgrade --- .../EventSourcingAggregateArrowExtension.kt | 4 ++-- .../MaterializedViewArrowExtension.kt | 4 ++-- .../application/SagaManagerArrowExtension.kt | 2 +- .../StateStoredAggregateArrowExtension.kt | 4 ++-- .../fmodel/application/MaterializedViewTest.kt | 11 ----------- ...SourcingAggregateArrowContextualExtension.kt | 2 +- .../MaterializedViewArrowContextualExtension.kt | 6 +++--- .../SagaManagerArrowContextualExtension.kt.kt | 2 +- ...teStoredAggregateArrowContextualExtension.kt | 6 +++--- .../MaterializedViewArrowContextualTest.kt | 11 +---------- .../StateStoredAggregateArrowContextualTest.kt | 17 ----------------- 11 files changed, 16 insertions(+), 53 deletions(-) diff --git a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowExtension.kt b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowExtension.kt index adfe2597..1d9cbb2b 100644 --- a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowExtension.kt +++ b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowExtension.kt @@ -17,7 +17,7 @@ package com.fraktalio.fmodel.application import arrow.core.Either -import arrow.core.continuations.either +import arrow.core.raise.either import com.fraktalio.fmodel.application.Error.CommandHandlingFailed import com.fraktalio.fmodel.application.Error.CommandPublishingFailed import kotlinx.coroutines.FlowPreview @@ -37,7 +37,7 @@ fun EventSourcingAggregate.handleWithEffect(command: C): Flow .fetchEvents() .computeNewEvents(command) .save() - .map { either { it } } + .map { either { it } } .catch { emit(either { raise(CommandHandlingFailed(command)) }) } /** diff --git a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowExtension.kt b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowExtension.kt index 5793c72b..b84c9cbb 100644 --- a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowExtension.kt +++ b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowExtension.kt @@ -17,8 +17,8 @@ package com.fraktalio.fmodel.application import arrow.core.Either -import arrow.core.continuations.catch -import arrow.core.continuations.either +import arrow.core.raise.catch +import arrow.core.raise.either import com.fraktalio.fmodel.application.Error.EventHandlingFailed import com.fraktalio.fmodel.application.Error.EventPublishingFailed import kotlinx.coroutines.flow.Flow diff --git a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowExtension.kt b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowExtension.kt index 55f8357e..caacae3b 100644 --- a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowExtension.kt +++ b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowExtension.kt @@ -17,7 +17,7 @@ package com.fraktalio.fmodel.application import arrow.core.Either -import arrow.core.continuations.either +import arrow.core.raise.either import com.fraktalio.fmodel.application.Error.ActionResultHandlingFailed import com.fraktalio.fmodel.application.Error.ActionResultPublishingFailed import kotlinx.coroutines.FlowPreview diff --git a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowExtension.kt b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowExtension.kt index 839856ec..c867756e 100644 --- a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowExtension.kt +++ b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowExtension.kt @@ -17,8 +17,8 @@ package com.fraktalio.fmodel.application import arrow.core.Either -import arrow.core.continuations.catch -import arrow.core.continuations.either +import arrow.core.raise.catch +import arrow.core.raise.either import com.fraktalio.fmodel.application.Error.CommandHandlingFailed import com.fraktalio.fmodel.application.Error.CommandPublishingFailed import kotlinx.coroutines.FlowPreview diff --git a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewTest.kt b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewTest.kt index 0acaab89..022a38df 100644 --- a/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewTest.kt +++ b/application-arrow/src/commonTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewTest.kt @@ -1,8 +1,6 @@ package com.fraktalio.fmodel.application import arrow.core.Either -import arrow.core.continuations.Effect -import arrow.core.continuations.toEither import com.fraktalio.fmodel.application.examples.numbers.NumberViewRepository import com.fraktalio.fmodel.application.examples.numbers.even.query.EvenNumberLockingViewRepository import com.fraktalio.fmodel.application.examples.numbers.even.query.EvenNumberViewRepository @@ -21,7 +19,6 @@ import com.fraktalio.fmodel.domain.examples.numbers.even.query.evenNumberView import com.fraktalio.fmodel.domain.examples.numbers.odd.query.oddNumberView import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe -import io.kotest.matchers.types.shouldBeInstanceOf /** * DSL - Given @@ -68,14 +65,6 @@ private infix fun Either>.thenStateAndVersion(expected: } -private suspend fun Effect.thenError() { - val error = when (val result = this.toEither()) { - is Either.Right -> throw AssertionError("Expected Either.Left, but found Either.Right with value ${result.value}") - is Either.Left -> result.value - } - error.shouldBeInstanceOf() -} - /** * Materialized View Test */ diff --git a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowContextualExtension.kt b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowContextualExtension.kt index 0e23e16b..21ee3e7a 100644 --- a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowContextualExtension.kt +++ b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregateArrowContextualExtension.kt @@ -1,7 +1,7 @@ package com.fraktalio.fmodel.application import arrow.core.Either -import arrow.core.continuations.either +import arrow.core.raise.either import com.fraktalio.fmodel.application.Error.CommandHandlingFailed import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.* diff --git a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualExtension.kt b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualExtension.kt index 86930e62..654a67dd 100644 --- a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualExtension.kt +++ b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualExtension.kt @@ -1,9 +1,9 @@ package com.fraktalio.fmodel.application import arrow.core.Either -import arrow.core.continuations.Raise -import arrow.core.continuations.catch -import arrow.core.continuations.either +import arrow.core.raise.Raise +import arrow.core.raise.catch +import arrow.core.raise.either import com.fraktalio.fmodel.application.Error.EventHandlingFailed import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch diff --git a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowContextualExtension.kt.kt b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowContextualExtension.kt.kt index c593cbe4..4c77af72 100644 --- a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowContextualExtension.kt.kt +++ b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/SagaManagerArrowContextualExtension.kt.kt @@ -1,7 +1,7 @@ package com.fraktalio.fmodel.application import arrow.core.Either -import arrow.core.continuations.either +import arrow.core.raise.either import com.fraktalio.fmodel.application.Error.ActionResultHandlingFailed import com.fraktalio.fmodel.application.Error.ActionResultPublishingFailed import kotlinx.coroutines.FlowPreview diff --git a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualExtension.kt b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualExtension.kt index b7d0c8f7..8ebcd94e 100644 --- a/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualExtension.kt +++ b/application-arrow/src/jvmMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualExtension.kt @@ -1,9 +1,9 @@ package com.fraktalio.fmodel.application import arrow.core.Either -import arrow.core.continuations.Raise -import arrow.core.continuations.catch -import arrow.core.continuations.either +import arrow.core.raise.Raise +import arrow.core.raise.catch +import arrow.core.raise.either import com.fraktalio.fmodel.application.Error.CommandHandlingFailed import com.fraktalio.fmodel.application.Error.CommandPublishingFailed import kotlinx.coroutines.FlowPreview diff --git a/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualTest.kt b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualTest.kt index be7ecbb0..5980c4c2 100644 --- a/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualTest.kt +++ b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/MaterializedViewArrowContextualTest.kt @@ -1,8 +1,6 @@ package com.fraktalio.fmodel.application import arrow.core.Either -import arrow.core.continuations.Effect -import arrow.core.continuations.toEither import com.fraktalio.fmodel.application.examples.numbers.even.query.EvenNumberViewRepository import com.fraktalio.fmodel.application.examples.numbers.even.query.evenNumberViewRepository import com.fraktalio.fmodel.domain.examples.numbers.api.Description @@ -17,15 +15,8 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf import kotlin.contracts.ExperimentalContracts -private suspend infix fun Effect.thenState(expected: S) { - val state = when (val result = this.toEither()) { - is Either.Right -> result.value - is Either.Left -> throw AssertionError("Expected Either.Right, but found Either.Left with value ${result.value}") - } - return state shouldBe expected -} -private suspend infix fun Either.thenState(expected: S) { +private infix fun Either.thenState(expected: S) { val state = when (val result = this) { is Either.Right -> result.value is Either.Left -> throw AssertionError("Expected Either.Right, but found Either.Left with value ${result.value}") diff --git a/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualTest.kt b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualTest.kt index f512777f..543f0994 100644 --- a/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualTest.kt +++ b/application-arrow/src/jvmTest/kotlin/com/fraktalio/fmodel/application/StateStoredAggregateArrowContextualTest.kt @@ -1,8 +1,6 @@ package com.fraktalio.fmodel.application import arrow.core.Either -import arrow.core.continuations.Effect -import arrow.core.continuations.toEither import com.fraktalio.fmodel.application.examples.numbers.even.command.EvenNumberStateRepository import com.fraktalio.fmodel.application.examples.numbers.even.command.evenNumberStateRepository import com.fraktalio.fmodel.domain.examples.numbers.api.Description @@ -19,13 +17,6 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.last import kotlin.contracts.ExperimentalContracts -private suspend fun Effect.thenError() { - val error = when (val result = this.toEither()) { - is Either.Right -> throw AssertionError("Expected Either.Left, but found Either.Right with value ${result.value}") - is Either.Left -> result.value - } - error.shouldBeInstanceOf() -} private fun Either.thenError() { val error = when (val result = this) { @@ -35,14 +26,6 @@ private fun Either.thenError() { error.shouldBeInstanceOf() } -private suspend infix fun Effect.thenState(expected: S) { - val state = when (val result = this.toEither()) { - is Either.Right -> result.value - is Either.Left -> throw AssertionError("Expected Either.Right, but found Either.Left with value ${result.value}") - } - return state shouldBe expected -} - private infix fun Either.thenState(expected: S) { val state = when (val result = this) { is Either.Right -> result.value