Deployer

trait Deployer {
  def deployRepository[F[_]: Async, ID: EntityIDCodec, S, E, Alg[_[_]], RepositoryAlg[_[_]]](
      repository: RepositoryInterpreter[F, ID, Alg, RepositoryAlg],
      behavior: BehaviorInterpreter[F, S, E, Alg],
      sideEffect: SideEffectInterpreter[F, S, Alg, RepositoryAlg]
    )(implicit
      nameProvider: EntityNameProvider[ID],
      commandProtocol: CommandProtocol[ID, Alg],
      eventApplier: EventApplier[S, E],
      parameters: DeploymentParameters[F, ID, S, E]
    ): Resource[F, Deployment[F, RepositoryAlg]]
}

Deployer brings everything together to “materialize” an entity repository. It returns a Deployment instance wrapped in a cats-effect Resource (to support finalization). The Deployment type is defined by implementers (aka. the runtime, e.g. PekkoDeployer), and is meant to provide a RepositoryAlg instance allowing for interactions with the deployed repository. It can also expose extra fields or methods that are relevant to the particular runtime.

deployRepository is parametrized with the following type parameters:

  • F[_]: abstract effectful context F encapsulating all values, e.g. IO[*]
  • Alg[_[_]]: algebra allowing interaction with the entity, e.g. BookingAlg[IO[*]]
  • RepositoryAlg[_[_]]: repository algebra, e.g. BookingRepositoryAlg[IO[*]]
  • ID: entity ID, e.g. final case class BookingID(id: UUID) extends AnyVal
  • S: entity state, e.g. Booking
  • E: entity event, e.g. BookingEvent

Repository operation is defined by the interpreted repository, behavior and side-effect algebras, following a strictly defined sequence:

  1. the interpreted repository is used to create a handle on the entity with the specified ID. This handle implements the entity algebra, using which the caller can interact with the entity.
  2. when a function of the entity algebra is invoked, the invocation is serialized using the commandProtocol and sent over the wire thanks to CommandSender. It is then decoded on the server side and run with the provided behavior interpreter: this typically involves reading the entity state (e.g. for validation) and writing events (which leads to a new version of the state via the eventApplier folding function)
  3. after events are written, a possible side-effect is triggered: this supports asynchronicity (i.e. starting fibers)
  4. the function finally returns to the caller with the result of the operation, encoded over the wire as a reply using commandProtocol and delivered back to the caller thanks to CommandSender.

See also sequence diagrams in Example App that represent this flow for a concrete entity.