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}")
}
© 2024 fraktalio d.o.o.