From e9f081e476fe7c3d7f911c36c621f5a0c87f3d65 Mon Sep 17 00:00:00 2001 From: Domenic Cassisi Date: Fri, 13 Dec 2024 17:57:09 +0100 Subject: [PATCH] MemoryView implementation -- Renaming and Arrow - TODO: missing arrow tests - TODO: Actor? --- application-arrow/api/application-arrow.api | 4 ++ .../EphemeralViewArrowExtension.kt | 35 +++++++++++++++ .../api/application-vanilla.api | 8 ++-- .../application/EphemeralViewExtension.kt | 14 ++++++ .../fmodel/application/MemoryViewExtension.kt | 14 ------ ...MemoryViewTest.kt => EphemeralViewTest.kt} | 26 +++++------ ...t => EvenNumberEphemeralViewRepository.kt} | 12 +++--- application/api/application.api | 43 ++++++++++++------- .../fmodel/application/EphemeralView.kt | 39 +++++++++++++++++ ...pository.kt => EphemeralViewRepository.kt} | 12 +++--- .../fmodel/application/MemoryView.kt | 39 ----------------- .../fraktalio/fmodel/application/Result.kt | 1 + 12 files changed, 150 insertions(+), 97 deletions(-) create mode 100644 application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/EphemeralViewArrowExtension.kt create mode 100644 application-vanilla/src/commonMain/kotlin/com/fraktalio/fmodel/application/EphemeralViewExtension.kt delete mode 100644 application-vanilla/src/commonMain/kotlin/com/fraktalio/fmodel/application/MemoryViewExtension.kt rename application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/application/{MemoryViewTest.kt => EphemeralViewTest.kt} (65%) rename application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/application/examples/numbers/even/query/{EvenNumberMemoryViewRepository.kt => EvenNumberEphemeralViewRepository.kt} (73%) create mode 100644 application/src/commonMain/kotlin/com/fraktalio/fmodel/application/EphemeralView.kt rename application/src/commonMain/kotlin/com/fraktalio/fmodel/application/{MemoryViewRepository.kt => EphemeralViewRepository.kt} (50%) delete mode 100644 application/src/commonMain/kotlin/com/fraktalio/fmodel/application/MemoryView.kt diff --git a/application-arrow/api/application-arrow.api b/application-arrow/api/application-arrow.api index 81838c95..69adbde9 100644 --- a/application-arrow/api/application-arrow.api +++ b/application-arrow/api/application-arrow.api @@ -1,3 +1,7 @@ +public final class com/fraktalio/fmodel/application/EphemeralViewArrowExtensionKt { + public static final fun handleWithEffect (Lcom/fraktalio/fmodel/application/ViewStateComputation;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + public final class com/fraktalio/fmodel/application/EventSourcingAggregateArrowExtensionKt { public static final fun handleOptimisticallyWithEffect (Lcom/fraktalio/fmodel/application/EventSourcingLockingAggregate;Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow; public static final fun handleOptimisticallyWithEffect (Lcom/fraktalio/fmodel/application/EventSourcingLockingAggregate;Ljava/lang/Object;Ljava/util/Map;)Lkotlinx/coroutines/flow/Flow; diff --git a/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/EphemeralViewArrowExtension.kt b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/EphemeralViewArrowExtension.kt new file mode 100644 index 00000000..db2be988 --- /dev/null +++ b/application-arrow/src/commonMain/kotlin/com/fraktalio/fmodel/application/EphemeralViewArrowExtension.kt @@ -0,0 +1,35 @@ +package com.fraktalio.fmodel.application + +import arrow.core.Either +import arrow.core.raise.catch +import arrow.core.raise.either +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.fold + + +suspend fun MV.handleWithEffect(query: Q): Either + where MV : ViewStateComputation, MV : EphemeralViewRepository { + + fun Q.fetchEventsWithEffect(): Either> = + either { + catch({ + fetchEvents() + }) { + raise(Error.FetchingEventsFailed(query, it)) + } + } + + suspend fun Flow.computeStateWithEffect(): Either = + either { + catch({ + fold(initialState) { s, e -> evolve(s, e) } + }) { + raise(Error.CalculatingNewViewStateFailed(this@computeStateWithEffect, it)) + } + } + + return either { + query.fetchEventsWithEffect().bind() + .computeStateWithEffect().bind() + } +} diff --git a/application-vanilla/api/application-vanilla.api b/application-vanilla/api/application-vanilla.api index ef809389..b64dceac 100644 --- a/application-vanilla/api/application-vanilla.api +++ b/application-vanilla/api/application-vanilla.api @@ -1,3 +1,7 @@ +public final class com/fraktalio/fmodel/application/EphemeralViewExtensionKt { + public static final fun handle (Lcom/fraktalio/fmodel/application/ViewStateComputation;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + public final class com/fraktalio/fmodel/application/EventSourcingAggregateActorExtensionKt { public static final fun handleConcurrently (Lcom/fraktalio/fmodel/application/EventSourcingAggregate;Lkotlinx/coroutines/flow/Flow;IILkotlinx/coroutines/CoroutineStart;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow; public static final fun handleConcurrently (Lcom/fraktalio/fmodel/application/EventSourcingAggregate;Lkotlinx/coroutines/flow/Flow;IILkotlinx/coroutines/CoroutineStart;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow; @@ -106,10 +110,6 @@ public final class com/fraktalio/fmodel/application/MaterializedViewExtensionKt public static final fun publishWithMetaDataTo (Lkotlinx/coroutines/flow/Flow;Lcom/fraktalio/fmodel/application/ViewStateComputation;)Lkotlinx/coroutines/flow/Flow; } -public final class com/fraktalio/fmodel/application/MemoryViewExtensionKt { - public static final fun handle (Lcom/fraktalio/fmodel/application/ViewStateComputation;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - public final class com/fraktalio/fmodel/application/SagaManagerActorExtensionKt { public static final fun handleConcurrently (Lcom/fraktalio/fmodel/application/SagaManager;Lkotlinx/coroutines/flow/Flow;IILkotlinx/coroutines/CoroutineStart;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow; public static synthetic fun handleConcurrently$default (Lcom/fraktalio/fmodel/application/SagaManager;Lkotlinx/coroutines/flow/Flow;IILkotlinx/coroutines/CoroutineStart;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; diff --git a/application-vanilla/src/commonMain/kotlin/com/fraktalio/fmodel/application/EphemeralViewExtension.kt b/application-vanilla/src/commonMain/kotlin/com/fraktalio/fmodel/application/EphemeralViewExtension.kt new file mode 100644 index 00000000..3c2be6ad --- /dev/null +++ b/application-vanilla/src/commonMain/kotlin/com/fraktalio/fmodel/application/EphemeralViewExtension.kt @@ -0,0 +1,14 @@ +package com.fraktalio.fmodel.application + +import kotlinx.coroutines.flow.fold + +/** + * Extension function - Handles the query of type [Q] + * + * @param query Query of type [Q] to be handled + * @return State of type [S] + * + * @author Domenic Cassisi + */ +suspend fun MV.handle(query: Q): S where MV : ViewStateComputation, MV : EphemeralViewRepository = + query.fetchEvents().fold(initialState) { s, e -> evolve(s, e) } diff --git a/application-vanilla/src/commonMain/kotlin/com/fraktalio/fmodel/application/MemoryViewExtension.kt b/application-vanilla/src/commonMain/kotlin/com/fraktalio/fmodel/application/MemoryViewExtension.kt deleted file mode 100644 index 8d59ea42..00000000 --- a/application-vanilla/src/commonMain/kotlin/com/fraktalio/fmodel/application/MemoryViewExtension.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.fraktalio.fmodel.application - -import kotlinx.coroutines.flow.fold - -/** - * Extension function - Handles the identifier of type [I] - * - * @param id Identifier of type [I] to be handled - * @return State of type [S] - * - * @author Domenic Cassisi - */ -suspend fun MV.handle(id: I): S where MV : ViewStateComputation, MV : MemoryViewRepository = - id.fetchEvents().fold(initialState) { s, e -> evolve(s, e) } diff --git a/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/application/MemoryViewTest.kt b/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/application/EphemeralViewTest.kt similarity index 65% rename from application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/application/MemoryViewTest.kt rename to application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/application/EphemeralViewTest.kt index 09cd1082..e51fb584 100644 --- a/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/application/MemoryViewTest.kt +++ b/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/application/EphemeralViewTest.kt @@ -1,7 +1,7 @@ package com.fraktalio.fmodel.application -import com.fraktalio.fmodel.application.examples.numbers.even.query.EvenNumberMemoryViewRepository -import com.fraktalio.fmodel.application.examples.numbers.even.query.evenNumberMemoryViewRepository +import com.fraktalio.fmodel.application.examples.numbers.even.query.EvenNumberEphemeralViewRepository +import com.fraktalio.fmodel.application.examples.numbers.even.query.evenNumberEphemeralViewRepository import com.fraktalio.fmodel.domain.IView import com.fraktalio.fmodel.domain.examples.numbers.api.Description import com.fraktalio.fmodel.domain.examples.numbers.api.EvenNumberState @@ -13,10 +13,10 @@ import io.kotest.matchers.shouldBe /** * DSL - Given */ -private suspend fun IView.given(repository: MemoryViewRepository, id: () -> I): S = - MemoryView( +private suspend fun IView.given(repository: EphemeralViewRepository, id: () -> I): S = + EphemeralView( view = this, - memoryViewRepository = repository + ephemeralViewRepository = repository ).handle(id()) /** @@ -29,29 +29,29 @@ private fun whenId(id: I): I = id */ private infix fun S.thenState(expected: S) = shouldBe(expected) -class MemoryViewTest : FunSpec({ +class EphemeralViewTest : FunSpec({ val evenView = evenNumberView() - val memoryViewRepository = evenNumberMemoryViewRepository() as EvenNumberMemoryViewRepository + val ephemeralViewRepository = evenNumberEphemeralViewRepository() as EvenNumberEphemeralViewRepository - test("Memory View - load number flow 1") { + test("Ephemeral View - load number flow 1") { with(evenView) { - given(memoryViewRepository) { + given(ephemeralViewRepository) { whenId(1) } thenState EvenNumberState(Description("Initial state, Number 2, Number 4"), NumberValue(6)) } } - test("Memory View - load number flow 2") { + test("Ephemeral View - load number flow 2") { with(evenView) { - given(memoryViewRepository) { + given(ephemeralViewRepository) { whenId(2) } thenState EvenNumberState(Description("Initial state, Number 4, Number 2"), NumberValue(2)) } } - test("Memory View - load non-existing number flow") { + test("Ephemeral View - load non-existing number flow") { with(evenView) { - given(memoryViewRepository) { + given(ephemeralViewRepository) { whenId(3) } thenState EvenNumberState(Description("Initial state"), NumberValue(0)) } diff --git a/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/application/examples/numbers/even/query/EvenNumberMemoryViewRepository.kt b/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/application/examples/numbers/even/query/EvenNumberEphemeralViewRepository.kt similarity index 73% rename from application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/application/examples/numbers/even/query/EvenNumberMemoryViewRepository.kt rename to application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/application/examples/numbers/even/query/EvenNumberEphemeralViewRepository.kt index 8e1a6a55..48a94464 100644 --- a/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/application/examples/numbers/even/query/EvenNumberMemoryViewRepository.kt +++ b/application-vanilla/src/commonTest/kotlin/com/fraktalio/fmodel/application/examples/numbers/even/query/EvenNumberEphemeralViewRepository.kt @@ -1,6 +1,6 @@ package com.fraktalio.fmodel.application.examples.numbers.even.query -import com.fraktalio.fmodel.application.MemoryViewRepository +import com.fraktalio.fmodel.application.EphemeralViewRepository import com.fraktalio.fmodel.domain.examples.numbers.api.Description import com.fraktalio.fmodel.domain.examples.numbers.api.NumberEvent import com.fraktalio.fmodel.domain.examples.numbers.api.NumberValue @@ -22,9 +22,9 @@ private var numberFlow2 = flowOf( ) /** - * Even number memory view implementation + * Even number ephemeral view implementation */ -class EvenNumberMemoryViewRepository : MemoryViewRepository { +class EvenNumberEphemeralViewRepository : EphemeralViewRepository { override fun Int.fetchEvents(): Flow { return when (this) { @@ -37,7 +37,7 @@ class EvenNumberMemoryViewRepository : MemoryViewRepository = - EvenNumberMemoryViewRepository() +fun evenNumberEphemeralViewRepository(): EphemeralViewRepository = + EvenNumberEphemeralViewRepository() diff --git a/application/api/application.api b/application/api/application.api index dc4a15ec..186db33a 100644 --- a/application/api/application.api +++ b/application/api/application.api @@ -7,6 +7,21 @@ public final class com/fraktalio/fmodel/application/ActionPublisher$DefaultImpls public static fun publish (Lcom/fraktalio/fmodel/application/ActionPublisher;Lkotlinx/coroutines/flow/Flow;Ljava/util/Map;)Lkotlinx/coroutines/flow/Flow; } +public abstract interface class com/fraktalio/fmodel/application/EphemeralView : com/fraktalio/fmodel/application/EphemeralViewRepository, com/fraktalio/fmodel/application/ViewStateComputation { +} + +public final class com/fraktalio/fmodel/application/EphemeralView$DefaultImpls { + public static fun computeNewState (Lcom/fraktalio/fmodel/application/EphemeralView;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +} + +public final class com/fraktalio/fmodel/application/EphemeralViewKt { + public static final fun EphemeralView (Lcom/fraktalio/fmodel/domain/IView;Lcom/fraktalio/fmodel/application/EphemeralViewRepository;)Lcom/fraktalio/fmodel/application/EphemeralView; +} + +public abstract interface class com/fraktalio/fmodel/application/EphemeralViewRepository { + public abstract fun fetchEvents (Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow; +} + public abstract class com/fraktalio/fmodel/application/Error : com/fraktalio/fmodel/application/Result { public abstract fun getThrowable ()Ljava/lang/Throwable; } @@ -110,6 +125,19 @@ public final class com/fraktalio/fmodel/application/Error$EventPublishingFailed public fun toString ()Ljava/lang/String; } +public final class com/fraktalio/fmodel/application/Error$FetchingEventsFailed : com/fraktalio/fmodel/application/Error { + public fun (Ljava/lang/Object;Ljava/lang/Throwable;)V + public final fun component1 ()Ljava/lang/Object; + public final fun component2 ()Ljava/lang/Throwable; + public final fun copy (Ljava/lang/Object;Ljava/lang/Throwable;)Lcom/fraktalio/fmodel/application/Error$FetchingEventsFailed; + public static synthetic fun copy$default (Lcom/fraktalio/fmodel/application/Error$FetchingEventsFailed;Ljava/lang/Object;Ljava/lang/Throwable;ILjava/lang/Object;)Lcom/fraktalio/fmodel/application/Error$FetchingEventsFailed; + public fun equals (Ljava/lang/Object;)Z + public final fun getId ()Ljava/lang/Object; + public fun getThrowable ()Ljava/lang/Throwable; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class com/fraktalio/fmodel/application/Error$FetchingStateFailed : com/fraktalio/fmodel/application/Error { public fun (Ljava/lang/Object;Ljava/lang/Throwable;)V public synthetic fun (Ljava/lang/Object;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -281,21 +309,6 @@ public final class com/fraktalio/fmodel/application/MaterializedViewKt { public static final fun materializedView (Lcom/fraktalio/fmodel/domain/IView;Lcom/fraktalio/fmodel/application/ViewStateRepository;)Lcom/fraktalio/fmodel/application/MaterializedView; } -public abstract interface class com/fraktalio/fmodel/application/MemoryView : com/fraktalio/fmodel/application/MemoryViewRepository, com/fraktalio/fmodel/application/ViewStateComputation { -} - -public final class com/fraktalio/fmodel/application/MemoryView$DefaultImpls { - public static fun computeNewState (Lcom/fraktalio/fmodel/application/MemoryView;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; -} - -public final class com/fraktalio/fmodel/application/MemoryViewKt { - public static final fun MemoryView (Lcom/fraktalio/fmodel/domain/IView;Lcom/fraktalio/fmodel/application/MemoryViewRepository;)Lcom/fraktalio/fmodel/application/MemoryView; -} - -public abstract interface class com/fraktalio/fmodel/application/MemoryViewRepository { - public abstract fun fetchEvents (Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow; -} - public abstract class com/fraktalio/fmodel/application/Result { } diff --git a/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/EphemeralView.kt b/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/EphemeralView.kt new file mode 100644 index 00000000..357406e5 --- /dev/null +++ b/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/EphemeralView.kt @@ -0,0 +1,39 @@ +package com.fraktalio.fmodel.application + +import com.fraktalio.fmodel.domain.IView + +/** + * EphemeralView is using/delegating a `view` / [ViewStateComputation]<[S], [E]> to handle events of type [E] without maintaining a state of projection(s). + * + * [EphemeralView] extends [ViewStateComputation] and [EphemeralViewRepository] interfaces, + * clearly communicating that it is composed out of these two behaviours. + * + * @param S Ephemeral View state of type [S] + * @param E Events of type [E] that are handled by this Ephemeral View + * @param Q Query of type [Q] + * + * @author Domenic Cassisi + */ +interface EphemeralView : ViewStateComputation, EphemeralViewRepository + +/** + * Ephemeral View constructor-like function. + * + * The Delegation pattern has proven to be a good alternative to implementation inheritance, and Kotlin supports it natively requiring zero boilerplate code. + * + * @param S Ephemeral View state of type [S] + * @param E Events of type [E] that are used internally to build/fold new state + * @param Q Identifier of type [Q] + * @property view A view component of type [IView]<[S], [E]> + * @property ephemeralViewRepository Interface for fetching events for [Q] - dependencies by delegation + * @return An object/instance of type [EphemeralView]<[S], [E], [Q]> + * + * @author Domenic Cassisi + */ +fun EphemeralView( + view: IView, + ephemeralViewRepository: EphemeralViewRepository +): EphemeralView = + object : EphemeralView, + EphemeralViewRepository by ephemeralViewRepository, + IView by view {} diff --git a/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/MemoryViewRepository.kt b/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/EphemeralViewRepository.kt similarity index 50% rename from application/src/commonMain/kotlin/com/fraktalio/fmodel/application/MemoryViewRepository.kt rename to application/src/commonMain/kotlin/com/fraktalio/fmodel/application/EphemeralViewRepository.kt index 353268c3..225410c6 100644 --- a/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/MemoryViewRepository.kt +++ b/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/EphemeralViewRepository.kt @@ -3,21 +3,21 @@ package com.fraktalio.fmodel.application import kotlinx.coroutines.flow.Flow /** - * Memory view repository interface + * Ephemeral view repository interface * * @param E Event - * @param I Identifier + * @param Q Query * - * @author Domenic + * @author Domenic Cassisi */ -fun interface MemoryViewRepository { +fun interface EphemeralViewRepository { /** * Fetch events * - * @receiver Identifier of type [I] + * @receiver Query of type [Q] * @return the Flow of events of type [E] */ - fun I.fetchEvents(): Flow + fun Q.fetchEvents(): Flow } diff --git a/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/MemoryView.kt b/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/MemoryView.kt deleted file mode 100644 index 13fc4af2..00000000 --- a/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/MemoryView.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.fraktalio.fmodel.application - -import com.fraktalio.fmodel.domain.IView - -/** - * Memory view is using/delegating a `view` / [ViewStateComputation]<[S], [E]> to handle events of type [E] without maintaining a state of projection(s). - * - * [MemoryView] extends [ViewStateComputation] and [MemoryViewRepository] interfaces, - * clearly communicating that it is composed out of these two behaviours. - * - * @param S Memory View state of type [S] - * @param E Events of type [E] that are handled by this Memory View - * @param I Identifier of type [I] - * - * @author Domenic Cassisi - */ -interface MemoryView : ViewStateComputation, MemoryViewRepository - -/** - * Memory View constructor-like function. - * - * The Delegation pattern has proven to be a good alternative to implementation inheritance, and Kotlin supports it natively requiring zero boilerplate code. - * - * @param S Memory View state of type [S] - * @param E Events of type [E] that are used internally to build/fold new state - * @param I Identifier of type [I] - * @property view A view component of type [IView]<[S], [E]> - * @property memoryViewRepository Interface for fetching events for [I] - dependencies by delegation - * @return An object/instance of type [MemoryView]<[S], [E], [I]> - * - * @author Domenic Cassisi - */ -fun MemoryView( - view: IView, - memoryViewRepository: MemoryViewRepository -): MemoryView = - object : MemoryView, - MemoryViewRepository by memoryViewRepository, - IView by view {} 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 aa34ad34..5bf4697f 100644 --- a/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/Result.kt +++ b/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/Result.kt @@ -55,6 +55,7 @@ sealed class Error : Result() { ) : Error() data class StoringStateFailed(val state: S, override val throwable: Throwable? = null) : Error() + data class FetchingEventsFailed(val id: I, override val throwable: Throwable?): Error() }