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

com.devsisters.shardcake.Messenger.scala Maven / Gradle / Ivy

package com.devsisters.shardcake

import com.devsisters.shardcake.errors.StreamCancelled
import zio._
import zio.stream.ZStream

/**
 * An interface to communicate with a remote entity
 * @tparam Msg the type of message that can be sent to this entity type
 */
trait Messenger[-Msg] {

  /**
   * Send a message without waiting for a response (fire and forget)
   */
  def sendDiscard(entityId: String)(msg: Msg): Task[Unit]

  /**
   * Send a message and wait for a response of type `Res`
   */
  def send[Res](entityId: String)(msg: Replier[Res] => Msg): Task[Res]

  /**
   * Send a message and receive a stream of responses of type `Res`.
   *
   * Note: The returned stream will fail with a `PodUnavailable` error if the remote entity is rebalanced while
   * streaming responses. See `sendStreamAutoRestart` for an alternative that will automatically restart the stream
   * in case of rebalance.
   */
  def sendAndReceiveStream[Res](entityId: String)(msg: StreamReplier[Res] => Msg): Task[ZStream[Any, Throwable, Res]]

  /**
   * Send a stream of messages.
   */
  def sendStream(entityId: String)(messages: ZStream[Any, Throwable, Msg]): Task[Unit]

  /**
   * Send a stream of messages and receive a stream of responses of type `Res`.
   */
  def sendStreamAndReceiveStream[Res](entityId: String)(
    messages: StreamReplier[Res] => ZStream[Any, Throwable, Msg]
  ): Task[ZStream[Any, Throwable, Res]]

  /**
   * Send a message and receive a stream of responses of type `Res` while restarting the stream when the remote entity
   * is rebalanced.
   *
   * To do so, we need a "cursor" so the stream of responses can be restarted where it ended before the rebalance. That
   * is, the first message sent to the remote entity contains the given initial cursor value and we extract an updated
   * cursor from the responses so that when the remote entity is rebalanced, a new message can be sent with the right
   * cursor according to what we've seen in the previous stream of responses.
   */
  def sendAndReceiveStreamAutoRestart[Cursor, Res](entityId: String, cursor: Cursor)(
    msg: (Cursor, StreamReplier[Res]) => Msg
  )(
    updateCursor: (Cursor, Res) => Cursor
  ): ZStream[Any, Throwable, Res] =
    ZStream
      .unwrap(sendAndReceiveStream[Res](entityId)(msg(cursor, _)))
      .either
      .mapAccum(cursor) {
        case (c, Right(res)) => updateCursor(c, res) -> Right(res)
        case (c, Left(err))  => (c, Left(c -> err))
      }
      .flatMap {
        case Right(res)                              => ZStream.succeed(res)
        case Left((lastSeenCursor, StreamCancelled)) =>
          ZStream.execute(ZIO.sleep(200.millis)) ++
            sendAndReceiveStreamAutoRestart(entityId, lastSeenCursor)(msg)(updateCursor)
        case Left((_, err))                          => ZStream.fail(err)
      }

  /**
   * Send a stream of messages and receive a stream of responses of type `Res` while restarting the stream when the
   * remote entity is rebalanced.
   *
   * To do so, we need a "cursor" so the stream of responses can be restarted where it ended before the rebalance. That
   * is, the first message sent to the remote entity contains the given initial cursor value and we extract an updated
   * cursor from the responses so that when the remote entity is rebalanced, a new message can be sent with the right
   * cursor according to what we've seen in the previous stream of responses.
   */
  def sendStreamAndReceiveStreamAutoRestart[Cursor, Res](entityId: String, cursor: Cursor)(
    msg: (Cursor, StreamReplier[Res]) => ZStream[Any, Throwable, Msg]
  )(
    updateCursor: (Cursor, Res) => Cursor
  ): ZStream[Any, Throwable, Res] =
    ZStream
      .unwrap(sendStreamAndReceiveStream[Res](entityId)(msg(cursor, _)))
      .either
      .mapAccum(cursor) {
        case (c, Right(res)) => updateCursor(c, res) -> Right(res)
        case (c, Left(err))  => (c, Left(c -> err))
      }
      .flatMap {
        case Right(res)                              => ZStream.succeed(res)
        case Left((lastSeenCursor, StreamCancelled)) =>
          ZStream.execute(ZIO.sleep(200.millis)) ++
            sendStreamAndReceiveStreamAutoRestart(entityId, lastSeenCursor)(msg)(updateCursor)
        case Left((_, err))                          => ZStream.fail(err)
      }
}

object Messenger {
  sealed trait MessengerTimeout
  object MessengerTimeout {
    case object NoTimeout                  extends MessengerTimeout
    case object InheritConfigTimeout       extends MessengerTimeout
    case class Timeout(duration: Duration) extends MessengerTimeout
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy