All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cultureamp.eventsourcing.Aggregate.kt Maven / Gradle / Ivy

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