com.cultureamp.eventsourcing.Aggregate.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kestrel Show documentation
Show all versions of kestrel Show documentation
Kotlin Framework for running event-sourced services
The newest version!
package com.cultureamp.eventsourcing
import org.joda.time.DateTime
import java.util.UUID
import kotlin.reflect.KClass
interface SimpleAggregate {
fun updated(event: UE): SimpleAggregate
fun update(command: UC): Either>
}
interface SimpleAggregateConstructor {
fun created(event: CE): SimpleAggregate
fun create(command: CC): Either
fun aggregateType(): String = this::class.companionClassName
}
interface SimpleAggregateWithProjection {
fun updated(event: UE): SimpleAggregateWithProjection
fun update(projection: P, command: UC): Either>
fun partial(projection: P): SimpleAggregate {
return object : SimpleAggregate {
override fun updated(event: UE): SimpleAggregate {
return [email protected](event).partial(projection)
}
override fun update(command: UC): Either> {
return update(projection, command)
}
}
}
}
interface SimpleAggregateConstructorWithProjection {
fun created(event: CE): SimpleAggregateWithProjection
fun create(projection: P, command: CC): Either
fun aggregateType(): String = this::class.companionClassName
fun partial(projection: P): SimpleAggregateConstructor {
return object : SimpleAggregateConstructor {
override fun created(event: CE): SimpleAggregate {
return [email protected](event).partial(projection)
}
override fun create(command: CC): Either {
return create(projection, command)
}
override fun aggregateType(): String = [email protected]()
}
}
}
interface Aggregate> {
fun updated(event: UE): Self
fun update(command: UC, metadata: M): Either>
companion object {
fun from(
aggregate: A,
update: A.(UC, M) -> Either>,
updated: A.(UE) -> A = { _ -> this }
): Aggregate> {
return object : Aggregate> {
override fun updated(event: UE): Aggregate {
val updatedAggregate = aggregate.updated(event)
return from(updatedAggregate, update, updated)
}
override fun update(command: UC, metadata: M): Either> {
return aggregate.update(command, metadata)
}
}
}
fun from(
aggregate: A,
update: A.(UC) -> Either>,
updated: A.(UE) -> A = { _ -> this }
): Aggregate> {
return object : Aggregate> {
override fun updated(event: UE): Aggregate {
val updatedAggregate = aggregate.updated(event)
return from(updatedAggregate, update, updated)
}
override fun update(command: UC, metadata: M): Either> {
return aggregate.update(command)
}
}
}
fun from(
aggregate: SimpleAggregate,
): Aggregate> {
return object : Aggregate> {
override fun updated(event: UE): Aggregate {
val updatedAggregate = aggregate.updated(event)
return from(updatedAggregate)
}
override fun update(command: UC, metadata: M): Either> {
return aggregate.update(command)
}
}
}
}
}
interface AggregateWithProjection> {
fun updated(event: UE): Self
fun update(projection: P, command: UC, metadata: M): Either>
fun partial(projection: P): Aggregate> {
return object : Aggregate> {
override fun updated(event: UE): Aggregate {
return [email protected](event).partial(projection)
}
override fun update(command: UC, metadata: M): Either> {
return update(projection, command, metadata)
}
}
}
}
interface AggregateConstructor> {
fun created(event: CE): Self
fun create(command: CC, metadata: M): Either>>
fun aggregateType(): String = this::class.companionClassName
companion object {
inline fun from(
noinline create: (CC, M) -> Either,
noinline update: A.(UC, M) -> Either>,
noinline created: (CE) -> A,
noinline updated: A.(UE) -> A = { _ -> this },
noinline aggregateType: () -> String = { A::class.simpleName!! }
): AggregateConstructor> {
val createReturningMultipleEvents: (CC, M) -> Either>> = { cc, m -> create(cc, m).map { it to emptyList() } }
return from(createReturningMultipleEvents, update, created, updated, aggregateType)
}
@JvmName("fromCreationCommandReturningMultipleEvents")
inline fun from(
noinline create: (CC, M) -> Either>>,
noinline update: A.(UC, M) -> Either>,
noinline created: (CE) -> A,
noinline updated: A.(UE) -> A = { _ -> this },
noinline aggregateType: () -> String = { A::class.simpleName!! }
): AggregateConstructor> {
return object : AggregateConstructor> {
override fun created(event: CE): Aggregate {
val createdAggregate = created(event)
return Aggregate.from(createdAggregate, update, updated)
}
override fun create(command: CC, metadata: M): Either>> = create(command, metadata)
override fun aggregateType() = aggregateType()
}
}
inline fun from(
noinline create: (CC) -> Either,
noinline update: A.(UC) -> Either>,
noinline created: (CE) -> A,
noinline updated: A.(UE) -> A = { _ -> this },
noinline aggregateType: () -> String = { A::class.simpleName!! }
): AggregateConstructor> {
val createReturningMultipleEvents: (CC) -> Either>> = { cc -> create(cc).map { it to emptyList() } }
return from(createReturningMultipleEvents, update, created, updated, aggregateType)
}
@JvmName("fromCreationCommandReturningMultipleEvents")
inline fun from(
noinline create: (CC) -> Either>>,
noinline update: A.(UC) -> Either>,
noinline created: (CE) -> A,
noinline updated: A.(UE) -> A = { _ -> this },
noinline aggregateType: () -> String = { A::class.simpleName!! }
): AggregateConstructor> {
return object : AggregateConstructor> {
override fun created(event: CE): Aggregate {
val createdAggregate = created(event)
return Aggregate.from(createdAggregate, update, updated)
}
override fun create(command: CC, metadata: M): Either>> = create(command)
override fun aggregateType() = aggregateType()
}
}
inline fun from(
simpleAggregateConstructor: SimpleAggregateConstructor
): AggregateConstructor> {
return object : AggregateConstructor> {
override fun created(event: CE): Aggregate {
val createdAggregate = simpleAggregateConstructor.created(event)
return Aggregate.from>(createdAggregate)
}
override fun create(command: CC, metadata: M): Either>> = simpleAggregateConstructor.create(command).map { it to emptyList() }
override fun aggregateType() = simpleAggregateConstructor.aggregateType()
}
}
inline fun fromStateless(
noinline create: (CC, M) -> Either,
noinline update: (UC, M) -> Either>,
instance: A,
noinline aggregateType: () -> String = { A::class.simpleName!! }
): AggregateConstructor> {
val createReturningMultipleEvents: (CC, M) -> Either>> = { cc, m -> create(cc, m).map { it to emptyList() } }
return fromStateless(createReturningMultipleEvents, update, instance, aggregateType)
}
@JvmName("fromStatelessCreationCommandReturningMultipleEvents")
inline fun fromStateless(
noinline create: (CC, M) -> Either>>,
noinline update: (UC, M) -> Either>,
instance: A,
noinline aggregateType: () -> String = { A::class.simpleName!! }
): AggregateConstructor> {
return from(create, { uc: UC, m: M -> update(uc, m) }, { instance }, { instance }, aggregateType)
}
}
}
interface AggregateConstructorWithProjection> {
fun created(event: CE): Self
fun create(projection: P, command: CC, metadata: M): Either>>
fun aggregateType(): String = this::class.companionClassName
fun partial(projection: P): AggregateConstructor> {
return object : AggregateConstructor> {
override fun created(event: CE): Aggregate {
return [email protected](event).partial(projection)
}
override fun create(command: CC, metadata: M): Either>> {
return create(projection, command, metadata)
}
override fun aggregateType(): String = [email protected]()
}
}
}
internal fun
AggregateConstructor>.create(
creationCommand: CC,
metadata: M,
eventStore: EventStore
): Either {
return create(creationCommand, metadata).map { initialEvents ->
created(initialEvents.first).updated(initialEvents.second) // called to ensure the create event handler doesn't throw any exceptions
val domainEvents = listOf(initialEvents.first) + initialEvents.second
val events = domainEvents.mapIndexed { index, domainEvent ->
Event(
id = UUID.randomUUID(),
aggregateId = creationCommand.aggregateId,
aggregateSequence = index + 1L,
aggregateType = aggregateType(),
createdAt = DateTime.now(),
metadata = metadata,
domainEvent = domainEvent
)
}
eventStore.sink(events, creationCommand.aggregateId)
}.flatten()
}
internal fun
AggregateConstructor>.update(
updateCommand: UC,
metadata: M,
events: List>,
eventStore: EventStore
): Either {
val upcastedEvents = events.map {
val domainEvent = it.domainEvent
val upcastEvent = domainEvent::class.annotations.filterIsInstance()
if(upcastEvent.size == 1) {
it.copy(domainEvent = upcastEvent.first().upcasting(domainEvent, metadata))
}else{
it
}
}
val aggregate = rehydrate(upcastedEvents)
return aggregate.flatMap {
it.update(updateCommand, metadata).flatMap { domainEvents ->
it.updated(domainEvents) // called to ensure the update event handler doesn't throw any exceptions
val offset = upcastedEvents.last().aggregateSequence + 1
val createdAt = DateTime()
val storableEvents = domainEvents.withIndex().map { (index, domainEvent) ->
Event(
id = UUID.randomUUID(),
aggregateId = updateCommand.aggregateId,
aggregateSequence = offset + index,
aggregateType = aggregateType(),
createdAt = createdAt,
metadata = metadata,
domainEvent = domainEvent
)
}
eventStore.sink(storableEvents, updateCommand.aggregateId)
}
}
}
@Suppress("UNCHECKED_CAST")
private fun
AggregateConstructor>.rehydrate(
events: List>
): Either> {
val creationEvent = events.first()
val creationDomainEvent = creationEvent.domainEvent as CE
val aggregate = if (creationEvent.aggregateType == aggregateType()) {
Right(created(creationDomainEvent))
} else {
Left(ConstructorTypeMismatch(aggregateType(), creationDomainEvent::class))
}
val updateEvents = events.drop(1).map { it.domainEvent as UE }
return aggregate.map { it.updated(updateEvents) }
}
@Suppress("UNCHECKED_CAST")
private fun Aggregate.updated(updateEvents: List): Aggregate {
return updateEvents.fold(this) { aggregate, updateEvent -> aggregate.updated(updateEvent) as Aggregate }
}
val KClass.companionClassName get() = if (isCompanion) this.java.declaringClass.simpleName!! else this::class.simpleName!!
data class ConstructorTypeMismatch(val aggregateType: String, val eventType: KClass) : CommandError
© 2015 - 2025 Weber Informatics LLC | Privacy Policy