
com.akkaserverless.javasdk.impl.eventsourcedentity.EventSourcedEntityRouter.scala Maven / Gradle / Ivy
/*
* Copyright 2021 Lightbend Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.akkaserverless.javasdk.impl.eventsourcedentity
import java.util.Optional
import com.akkaserverless.javasdk.eventsourcedentity.CommandContext
import com.akkaserverless.javasdk.eventsourcedentity.EventContext
import com.akkaserverless.javasdk.eventsourcedentity.EventSourcedEntity
import com.akkaserverless.javasdk.impl.EntityExceptions
import com.akkaserverless.javasdk.impl.effect.SecondaryEffectImpl
import com.akkaserverless.javasdk.impl.eventsourcedentity.EventSourcedEntityEffectImpl.EmitEvents
import com.akkaserverless.javasdk.impl.eventsourcedentity.EventSourcedEntityEffectImpl.NoPrimaryEffect
object EventSourcedEntityRouter {
final case class CommandResult(
events: Vector[Any],
secondaryEffect: SecondaryEffectImpl,
snapshot: Option[Any],
endSequenceNumber: Long)
final case class CommandHandlerNotFound(commandName: String) extends RuntimeException
final case class EventHandlerNotFound(eventClass: Class[_]) extends RuntimeException
}
/**
* @tparam S
* the type of the managed state for the entity Not for manual user extension or interaction
*
* The concrete EventSourcedEntityRouter
is generated for the specific entities defined in Protobuf.
*/
abstract class EventSourcedEntityRouter[S, E <: EventSourcedEntity[S]](protected val entity: E) {
import EventSourcedEntityRouter._
private var state: Option[S] = None
/** INTERNAL API */
// "public" api against the impl/testkit
def _stateOrEmpty(): S = state match {
case None =>
val emptyState = entity.emptyState()
// null is allowed as emptyState
state = Some(emptyState)
emptyState
case Some(state) => state
}
private def setState(newState: S): Unit =
state = Option(newState)
/** INTERNAL API */
// "public" api against the impl/testkit
final def _internalHandleSnapshot(snapshot: S): Unit = setState(snapshot)
/** INTERNAL API */
// "public" api against the impl/testkit
final def _internalHandleEvent(event: Object, context: EventContext): Unit = {
entity._internalSetEventContext(Optional.of(context))
try {
val newState = handleEvent(_stateOrEmpty(), event)
setState(newState)
} catch {
case EventHandlerNotFound(eventClass) =>
throw new IllegalArgumentException(s"Unknown event type [$eventClass] on ${entity.getClass}")
} finally {
entity._internalSetEventContext(Optional.empty())
}
}
/** INTERNAL API */
// "public" api against the impl/testkit
final def _internalHandleCommand(
commandName: String,
command: Any,
context: CommandContext,
snapshotEvery: Int,
eventContextFactory: Long => EventContext): CommandResult = {
val commandEffect =
try {
entity._internalSetCommandContext(Optional.of(context))
handleCommand(commandName, _stateOrEmpty(), command, context).asInstanceOf[EventSourcedEntityEffectImpl[Any]]
} catch {
case CommandHandlerNotFound(name) =>
throw new EntityExceptions.EntityException(
context.entityId(),
context.commandId(),
commandName,
s"No command handler found for command [$name] on ${entity.getClass}")
} finally {
entity._internalSetCommandContext(Optional.empty())
}
var currentSequence = context.sequenceNumber()
commandEffect.primaryEffect match {
case EmitEvents(events) =>
var shouldSnapshot = false
events.foreach { event =>
try {
entity._internalSetEventContext(Optional.of(eventContextFactory(currentSequence)))
val newState = handleEvent(_stateOrEmpty(), event)
if (newState == null)
throw new IllegalArgumentException("Event handler must not return null as the updated state.")
setState(newState)
} catch {
case EventHandlerNotFound(eventClass) =>
throw new IllegalArgumentException(s"Unknown event type [$eventClass] on ${entity.getClass}")
} finally {
entity._internalSetEventContext(Optional.empty())
}
currentSequence += 1
shouldSnapshot = shouldSnapshot || (snapshotEvery > 0 && currentSequence % snapshotEvery == 0)
}
// snapshotting final state since that is the "atomic" write
// emptyState can be null but null snapshot should not be stored, but that can't even
// happen since event handler is not allowed to return null as newState
val endState = _stateOrEmpty()
val snapshot =
if (shouldSnapshot) Option(endState)
else None
try {
// side effect callbacks may want to access context or components which is valid
entity._internalSetCommandContext(Optional.of(context))
CommandResult(events.toVector, commandEffect.secondaryEffect(endState), snapshot, currentSequence)
} finally {
entity._internalSetCommandContext(Optional.empty())
}
case NoPrimaryEffect =>
try {
// side effect callbacks may want to access context or components which is valid
entity._internalSetCommandContext(Optional.of(context))
CommandResult(Vector.empty, commandEffect.secondaryEffect(_stateOrEmpty()), None, context.sequenceNumber())
} finally {
entity._internalSetCommandContext(Optional.empty())
}
}
}
def handleEvent(state: S, event: Any): S
def handleCommand(commandName: String, state: S, command: Any, context: CommandContext): EventSourcedEntity.Effect[_]
def entityClass: Class[_] = entity.getClass
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy