home..

The Playground

Learning by example | github | blog

import kotlinx.coroutines.flow.* import com.fraktalio.fmodel.domain.Decider import com.fraktalio.fmodel.application.EventRepository import com.fraktalio.fmodel.application.EventSourcingAggregate import com.fraktalio.fmodel.application.eventSourcingAggregate import com.fraktalio.fmodel.application.StateRepository import com.fraktalio.fmodel.application.StateStoredAggregate import com.fraktalio.fmodel.application.stateStoredAggregate import com.fraktalio.fmodel.application.* // ################################################################################################ // ############################################ Domain ############################################ // ################################################################################################ // ### - The domain model is fully isolated from the application model and API-related concerns ### // ### - It represents a pure declaration of the program logic ### // ################################################################################################ // ############################# The various data of our domain model ############################# // ################################################################################################ // ############ Common Values ############ @JvmInline value class Description(val get: String) { operator fun plus(param: Description) = Description("${this.get} + ${param.get}") operator fun minus(param: Description) = Description("${this.get} - ${param.get}") } @JvmInline value class NumberValue(val get: Int) { operator fun plus(param: NumberValue) = NumberValue(this.get + param.get) operator fun minus(param: NumberValue) = NumberValue(this.get - param.get) } // ############ Commands ############### abstract class NumberCommand { abstract val description: Description abstract val value: NumberValue } sealed class EvenNumberCommand : NumberCommand() data class AddEvenNumber( override val description: Description, override val value: NumberValue ) : EvenNumberCommand() data class SubtractEvenNumber( override val description: Description, override val value: NumberValue ) : EvenNumberCommand() // ############ Events ############### abstract class NumberEvent { abstract val description: Description abstract val value: NumberValue } sealed class EvenNumberEvent : NumberEvent() data class EvenNumberAdded( override val description: Description, override val value: NumberValue ) : EvenNumberEvent() data class EvenNumberSubtracted( override val description: Description, override val value: NumberValue ) : EvenNumberEvent() // ############ State ############### sealed class NumberState { abstract val description: Description abstract val value: NumberValue } data class EvenNumberState( override val description: Description, override val value: NumberValue ) : NumberState() // ######################### The behaviour of our domain / Program logic ########################## // ################################################################################################ // ############ A Decider ############ val evenNumberDecider: Decider<EvenNumberCommand?, EvenNumberState, EvenNumberEvent?> = Decider( initialState = EvenNumberState(Description("Initial state"), NumberValue(0)), decide = { c, s -> if (c != null && c.value.get > 1000) flow<EvenNumberEvent> { throw UnsupportedOperationException("Sorry") } else when (c) { is AddEvenNumber -> flowOf(EvenNumberAdded(c.description, c.value + s.value)) is SubtractEvenNumber -> flowOf(EvenNumberSubtracted(c.description, s.value - c.value)) null -> emptyFlow() } }, evolve = { s, e -> when (e) { is EvenNumberAdded -> EvenNumberState(s.description + e.description, e.value) is EvenNumberSubtracted -> EvenNumberState(s.description - e.description, e.value) null -> s } } ) // ################################################################################################ // #### The application module orchestrates the execution of the logic by: #### // #### - loading current state, #### // #### - executing domain components and #### // #### - storing new state #### // ################################################################################################ // ################################################################################################ // #### Demonstrating two approaches by using single/same Decider (domain model): #### // #### - Event-sourced systems are storing the Events in immutable storage by only appending ### // #### - State-stored systems are storing the new State by overwriting the previous State #### // ################################################################################################ // ################################################################################################ // ######################## 1. Application and Infrastructure (Event-sourced) ##################### // ################################################################################################ // ############## A very simple event store/db ############## private var evenNumberEventStorage: List<EvenNumberEvent?> = emptyList() // ############## Concrete implementation of the EventRepository ############## // `EventRepository` interface: https://github.com/fraktalio/fmodel/blob/main/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventRepository.kt class EvenNumberRepository : EventRepository<EvenNumberCommand?, EvenNumberEvent?> { override fun EvenNumberCommand?.fetchEvents(): Flow<EvenNumberEvent?> = evenNumberEventStorage.asFlow() override suspend fun EvenNumberEvent?.save(): EvenNumberEvent? { evenNumberEventStorage = evenNumberEventStorage.plus(this) return this } } // ################################################################################################ // ######################## 2. Application and Infrastructure (State-stored / Traditional) ######## // ################################################################################################ // ############## A very simple state store/db ############## private var evenNumberStateStorage: EvenNumberState = EvenNumberState(Description("0"), NumberValue(0)) // ############## Concrete implementation of the StateRepository ############## // `StateRepository` interface: https://github.com/fraktalio/fmodel/blob/main/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateRepository.kt class EvenNumberStateRepository : StateRepository<EvenNumberCommand?, EvenNumberState> { override suspend fun EvenNumberCommand?.fetchState(): EvenNumberState = evenNumberStateStorage override suspend fun EvenNumberState.save(): EvenNumberState { evenNumberStateStorage = this return evenNumberStateStorage } } // ################################################################################################ // ##################################### Main method/function ##################################### // ################################################################################################ suspend fun main(args: Array<String>) { // ########################################## // ############ 1. Event-sourced ############ // ########################################## val evenNumberESRepository = EvenNumberRepository() // An event sourced aggregate // `eventSourcingAggregate` factory function: https://github.com/fraktalio/fmodel/blob/main/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregate.kt val eventSourcingAggregate = eventSourcingAggregate( decider = evenNumberDecider, eventRepository = evenNumberESRepository ) // Demonstrating different APIs that are available on EventSourcingAggregate: println("### The Event-Sourced approach ###") println("new events being stored: ${eventSourcingAggregate.handle(AddEvenNumber(Description("2"), NumberValue(2))).toList()}") println("new events being stored: ${eventSourcingAggregate.handle(flowOf(AddEvenNumber(Description("4"), NumberValue(4)), AddEvenNumber(Description("6"), NumberValue(6)))).toList()}") println("new events being stored: ${SubtractEvenNumber(Description("8"), NumberValue(8)).publishTo(eventSourcingAggregate).toList()}") println("new events being stored: ${flowOf(AddEvenNumber(Description("10"), NumberValue(10)), AddEvenNumber(Description("12"), NumberValue(12))).publishTo(eventSourcingAggregate).toList()}") println("final state of the event-storage: ${evenNumberEventStorage}") // ########################################## // ############ 2. State-stored ############# // ########################################## val evenNumberStateRepository = EvenNumberStateRepository() // A state-stored aggregate // `stateStoredAggregate` factory function: https://github.com/fraktalio/fmodel/blob/main/application/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregate.kt val stateStoredAggregate = stateStoredAggregate( decider = evenNumberDecider, stateRepository = evenNumberStateRepository ) println("-") // Demonstrating different APIs that are available on StateStoredAggregate: println("### The State-Stored/Traditional approach ###") println("new state being stored: ${stateStoredAggregate.handle(AddEvenNumber(Description("2"), NumberValue(2)))}") println("new state being stored: ${stateStoredAggregate.handle(flowOf(AddEvenNumber(Description("4"), NumberValue(4)), AddEvenNumber(Description("6"), NumberValue(6)))).toList()}") println("new state being stored: ${SubtractEvenNumber(Description("8"), NumberValue(8)).publishTo(stateStoredAggregate)}") println("new state being stored: ${flowOf(AddEvenNumber(Description("10"), NumberValue(10)), AddEvenNumber(Description("12"), NumberValue(12))).publishTo(stateStoredAggregate).toList()}") println("final state of the state-storage: ${evenNumberStateStorage}") }
© 2023 fraktalio d.o.o.