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

org.elder.sourcerer.kotlin.EventStreams.kt Maven / Gradle / Ivy

package org.elder.sourcerer.kotlin

import org.elder.sourcerer.AggregateRepository
import org.elder.sourcerer.CommandFactory
import org.elder.sourcerer.CommandResponse
import org.elder.sourcerer.DefaultCommandFactory
import org.elder.sourcerer.ExpectedVersion
import org.elder.sourcerer.ImmutableAggregate
import org.elder.sourcerer.OperationHandler
import org.elder.sourcerer.OperationHandlerOperation
import org.elder.sourcerer.Operations
import org.elder.sourcerer.functions.UpdateHandlerAggregate

/**
 * Allows for creating, updating or appending to eventstore streams.
 *
 * Kotlin wrapper for CommandFactory that simplifies its usage.
 */
class EventStreams(
        private val commandFactory: CommandFactory
) {

    /**
     * Create EventStreams that uses a DefaultCommandFactory.
     */
    constructor(
            aggregateRepository: AggregateRepository
    ) : this(DefaultCommandFactory(aggregateRepository))

    /**
     * Create a new stream.
     *
     * @param id the aggregate id
     * @param conflictStrategy what to do if aggregate already exists. Default is to fail
     * @param create operations to perform on the aggregate
     */
    fun create(
            id: String,
            conflictStrategy: CreateConflictStrategy = CreateConflictStrategy.FAIL,
            create: (ImmutableAggregate) -> ImmutableAggregate
    ): CommandResponse {
        return CommandResponse.of(
                commandFactory
                        .fromOperation(constructOf(UpdateHandlerAggregate { state ->
                            create(state)
                        }))
                        .setAggregateId(id)
                        .setAtomic(true)
                        .setExpectedVersion(ExpectedVersion.notCreated())
                        .setIdempotentCreate(conflictStrategy.idempotentCreate)
                        .run())
    }

    /**
     * Update existing stream.
     *
     * @param id the aggregate id
     * @param expectedVersion expected aggregate version. By default will fail if stream does not
     * exist
     * @param update operations to perform on the aggregate
     */
    fun update(
            id: String,
            expectedVersion: ExpectedVersion = ExpectedVersion.anyExisting(),
            update: (ImmutableAggregate) -> ImmutableAggregate
    ): CommandResponse {
        return CommandResponse.of(
                commandFactory
                        .fromOperation(updateOf(update, expectedVersion))
                        .setAggregateId(id)
                        .setAtomic(true)
                        .setExpectedVersion(expectedVersion)
                        .run())
    }

    /**
     * Append events to stream.
     *
     * Stream may be new or existing. No validation is performed, and nothing is read.
     *
     * @param id the aggregate id
     * @param operation operations to perform to create events
     */
    fun append(id: String, operation: () -> List): CommandResponse {
        return CommandResponse.of(
                commandFactory
                        .fromOperation(Operations.appendOf(operation))
                        .setAggregateId(id)
                        .run())
    }

    private fun updateOf(
            update: (ImmutableAggregate) -> ImmutableAggregate,
            expectedVersion: ExpectedVersion
    ): OperationHandlerOperation {
        return OperationHandlerOperation(
                OperationHandler { agg, _ -> update(agg!!).events() },
                true,
                false,
                expectedVersion,
                true)
    }

    private fun constructOf(
            operation: UpdateHandlerAggregate
    ): OperationHandlerOperation {
        return OperationHandlerOperation(
                operation,
                true,
                false,
                ExpectedVersion.notCreated(),
                true)
    }
}

/**
 * Strategy for when trying to create an aggregate that already exists.
 */
enum class CreateConflictStrategy(internal val idempotentCreate: Boolean) {
    /**
     * Throw UnexpectedVersionException.
     */
    FAIL(idempotentCreate = false),
    /**
     * Do nothing.
     */
    NOOP(idempotentCreate = true);
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy