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

endless.core.entity.SideEffect.scala Maven / Gradle / Ivy

The newest version!
package endless.core.entity
import cats.Applicative
import cats.kernel.Eq
import cats.syntax.eq.*

/** `SideEffect[F, S, Alg]` represents a side-effect applied in context `F`. The side-effect is
  * triggered just after events persistence if any, or after some reads for a read-only command. The
  * mode of interpretation is determined by the `runModeFor` method, which defaults to `Async` but
  * can be overridden. The passed `Effector` can be used to access entity state and algebra and to
  * control passivation.
  * @tparam F
  *   effect type
  * @tparam S
  *   entity state
  * @tparam Alg
  *   entity algebra
  */
trait SideEffect[F[_], S, Alg[_[_]]] {
  def apply(trigger: SideEffect.Trigger, effector: Effector[F, S, Alg]): F[Unit]

  def runModeFor(trigger: SideEffect.Trigger, state: Option[S])(implicit
      applicative: Applicative[F]
  ): F[SideEffect.RunMode] = Applicative[F].pure(SideEffect.RunMode.Async)
}

object SideEffect {
  def unit[F[_]: Applicative, S, Alg[_[_]]]: SideEffect[F, S, Alg] =
    (_: Trigger, _: Effector[F, S, Alg]) => Applicative[F].unit

  /** Trigger for the invocation of a side-effect: this allows for differentiated behavior depending
    * on the context in which the side-effect is triggered.
    */
  sealed trait Trigger {
    def isAfterPersistence: Boolean = this === Trigger.AfterPersistence
    def isAfterRead: Boolean = this === Trigger.AfterRead
    def isAfterRecovery: Boolean = this === Trigger.AfterRecovery
  }
  object Trigger {

    /** Triggered just after events or state persistence */
    case object AfterPersistence extends Trigger

    /** Triggered just after processing a read-only command (no events were written, the state
      * hasn't changed)
      */
    case object AfterRead extends Trigger

    /** Triggered just after recovery */
    case object AfterRecovery extends Trigger

    implicit val eqTrigger: Eq[Trigger] = Eq.fromUniversalEquals
  }

  /** Run mode for a side-effect: `Async` (default value) means that the side-effect is triggered in
    * "fire & forget" mode, while `Sync` means it is run to completion before any other command is
    * processed by the entity.
    */
  sealed trait RunMode
  object RunMode {

    /** Run to completion before any other command is processed by the entity.
      *
      * @note
      *   This mode should in most cases not be used for long-running side-effects, as it can hurt
      *   availability of the entity for command processing.
      */
    case object Sync extends RunMode

    /** Run in "fire & forget" mode.
      *
      * @note
      *   This mode requires careful consideration of the side-effect's concurrency and idempotency,
      *   as there is no limit on the number of invocations running simultaneously at any one time.
      */
    case object Async extends RunMode
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy