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

net.kemitix.eip.zio.MessageChannel.scala Maven / Gradle / Ivy

The newest version!
package net.kemitix.eip.zio

import zio.{UIO, ZIO}

/**
  * A channel connecting a Sender and Receiver of Messages of type Body.
  */
object MessageChannel {

  // channels that can abort with errors from the Sender
  type EChannel[R, E, Body] = ZIO[R, Option[E], Message[Body]] => Unit
  type EChannelHandle[R, E] = zio.stream.ZStream[R, E, Unit]
  type ESender[R, E, Body]  = EChannel[R, E, Body] => ZIO[R, E, Unit]

  // channels that can't 'fail' (Uninterruptable)
  type UChannel[R, Body]  = EChannel[R, Nothing, Body]
  type UChannelHandle[R]  = EChannelHandle[R, Nothing]
  type USender[R, Body]   = ESender[R, Nothing, Body]
  type UReceiver[R, Body] = Message[Body] => ZIO[R, Nothing, Unit]

  // Aliases for uninterruptable channels - the proposed default
  type Channel[R, Body]  = UChannel[R, Body]
  type ChannelHandle[R]  = UChannelHandle[R]
  type Sender[R, Body]   = USender[R, Body]
  type Receiver[R, Body] = UReceiver[R, Body]

  type Forwarder[R, BodyIn, BodyOut] =
    Message[BodyIn] => ZIO[R, Nothing, Message[BodyOut]]

  object Syntax {

    implicit class ExtendUSender[RSend, Body](sender: USender[RSend, Body]) {
      def =>>[RReceive](receiver: MessageChannel.UReceiver[RReceive, Body])
        : EChannelHandle[RSend with RReceive, Nothing] =
        MessageChannel.pointToPoint(sender)(receiver)
    }

    implicit class ExtendESender[RSend, E, Body](
        sender: ESender[RSend, E, Body]) {
      def =>>[RReceive](receiver: MessageChannel.Receiver[RReceive, Body])
        : EChannelHandle[RSend with RReceive, E] =
        MessageChannel.pointToPoint(sender)(receiver)
    }

  }

  // Normal shutdown of a channel
  def endChannel[Body](channel: Channel[Any, Body]): UIO[Unit] =
    UIO(channel(ZIO.fail(None)))

  // Abnormal termination of a channel
  def abortChannel[R, E, Body](channel: EChannel[R, E, Body])(
      error: E): UIO[Unit] = UIO(channel(ZIO.fail(Some(error))))

  // Dispatch a message into the channel from the Sender
  def send[RSend, Body](channel: Channel[RSend, _ >: Body])(
      message: Message[Body]): UIO[Unit] =
    UIO(channel(ZIO.succeed(message)))

  /**
    * Use Messaging to make asynchronous calls or transfer documents/events.
    *
    * https://www.enterpriseintegrationpatterns.com/patterns/messaging/PointToPointChannel.html
    *
    * Creates a Message Channel from an asynchronous callback that can be called multiple times
    * by the sender to send a Message to the receiver.
    *
    * The sender is a callback that returns an effect. It will be forked onto a new Fiber.
    *
    * The sender sends Messages using the callback, wrapping the message within a successful ZIO.
    *
    * The channel can be shutdown be a call to [[MessageChannel.endChannel]].
    *
    * Unlike the EIP Message Channel, there can only be a single receiver.
    *
    * @param sender A callback that returns an effect
    * @param receiver A function that will be called for each Message
    * @tparam Body The type of the Message body
    * @tparam RSend The environment for the sender
    * @tparam RReceive The environment for the receiver
    * @tparam ESend The expected error type that can be returned by the Sender
    * @return a Channel
    */
  def pointToPoint[R, RSend >: R, RReceive >: R, E, ESend <: E, Body](
      sender: ESender[RSend, E, Body])(
      receiver: UReceiver[RReceive, Body]
  ): EChannelHandle[RSend with RReceive, E] = {
    pointToPointPar(1)(sender)(receiver)
  }
  def pointToForwarderToPoint[R,
                              RSend >: R,
                              RForward >: R,
                              RReceive >: R,
                              E,
                              ESend <: E,
                              BodyIn,
                              BodyOut](sender: ESender[RSend, ESend, BodyIn])(
      forwarder: Forwarder[RForward, BodyIn, BodyOut])(
      receiver: UReceiver[RReceive, BodyOut]): EChannelHandle[R, E] =
    pointToForwarderToPointPar(1, 1)(sender)(forwarder)(receiver)

  /**
    * See [[MessageChannel.pointToPoint]]
    *
    * Executes 'n' receivers running in parallel.
    *
    * @param n the number of parallel receivers
    */
  def pointToPointPar[R, RSend >: R, RReceive >: R, E, ESend <: E, Body](
      n: Int)(sender: ESender[RSend, ESend, Body])(
      receiver: UReceiver[RReceive, Body]): EChannelHandle[R, E] =
    zio.stream.ZStream
      .effectAsyncM((channel: EChannel[R, E, Body]) =>
        sender(channel).fork.unit)
      .mapMPar(n)(receiver)

  def pointToForwarderToPointPar[R,
                                 RSend >: R,
                                 RForward >: R,
                                 RReceive >: R,
                                 E,
                                 ESend <: E,
                                 BodyIn,
                                 BodyOut](nf: Int, nr: Int)(
      sender: ESender[RSend, ESend, BodyIn])(
      forwarder: Forwarder[RForward, BodyIn, BodyOut])(
      receiver: UReceiver[RReceive, BodyOut]): EChannelHandle[R, E] =
    zio.stream.ZStream
      .effectAsyncM((channel: EChannel[R, E, BodyIn]) =>
        sender(channel).fork.unit)
      .mapMPar(nf)(forwarder)
      .mapMPar(nr)(receiver)

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy