com.evolutiongaming.kafka.journal.conversions.KafkaWrite.scala Maven / Gradle / Ivy
The newest version!
package com.evolutiongaming.kafka.journal.conversions
import cats.{Monad, ~>}
import cats.data.{NonEmptyList => Nel}
import cats.syntax.all._
import com.evolutiongaming.catshelper.MonadThrowable
import com.evolutiongaming.kafka.journal.PayloadAndType._
import com.evolutiongaming.kafka.journal._
import com.evolutiongaming.smetrics.MeasureDuration
import play.api.libs.json.{JsValue, Json, Writes}
import scala.annotation.tailrec
trait KafkaWrite[F[_], A] {
def apply(events: Events[A]): F[PayloadAndType]
}
object KafkaWrite {
def summon[F[_], A](implicit kafkaWrite: KafkaWrite[F, A]): KafkaWrite[F, A] = kafkaWrite
implicit def payloadKafkaWrite[F[_] : MonadThrowable](implicit
eventsToBytes: ToBytes[F, Events[Payload]],
payloadJsonToBytes: ToBytes[F, PayloadJson[JsValue]]
): KafkaWrite[F, Payload] = {
events: Events[Payload] => {
def eventJson(event: Event[Payload]): Option[Event[Payload.TextOrJson]] = {
event.payload.fold {
event.copy(payload = none[Payload.TextOrJson]).some
} {
case _: Payload.Binary => none[Event[Payload.TextOrJson]]
case a: Payload.TextOrJson => event.as(a).some
}
}
@tailrec
def eventJsons(events: List[Event[Payload]], result: List[Event[Payload.TextOrJson]]): List[Event[Payload.TextOrJson]] =
events match {
case Nil => result.reverse
case head :: tail => eventJson(head) match {
case Some(x) => eventJsons(tail, x :: result)
case None => List.empty[Event[Payload.TextOrJson]]
}
}
def toEventJsonPayload(payload: Payload.TextOrJson) = {
def of[A : Writes](a: A, payloadType: PayloadType.TextOrJson) =
EventJsonPayloadAndType(Json.toJson(a), payloadType)
payload match {
case a: Payload.Json => of(a.value, PayloadType.Json)
case a: Payload.Text => of(a.value, PayloadType.Text)
}
}
def payloadAndType(eventJsons: List[Event[Payload.TextOrJson]]) = {
eventJsons match {
case head :: tail =>
val jsonEvents = events.copy(events = Nel(head, tail))
val jsonKafkaWrite = KafkaWrite.writeJson(toEventJsonPayload, payloadJsonToBytes)
jsonKafkaWrite(jsonEvents)
case Nil =>
withErrorAdapted(events) {
eventsToBytes(events).map { PayloadAndType(_, PayloadType.Binary) }
}
}
}
payloadAndType(eventJsons(events.events.toList, List.empty))
}
}
def writeJson[F[_] : MonadThrowable, A, B](
toEventJsonPayload: A => EventJsonPayloadAndType[B],
payloadJsonToBytes: ToBytes[F, PayloadJson[B]]
): KafkaWrite[F, A] = {
events: Events[A] => {
def eventJson(event: Event[A]): EventJson[B] = {
def of(payloadType: Option[PayloadType.TextOrJson], payload: Option[B]) = {
EventJson(event.seqNr, event.tags, payloadType, payload)
}
event.payload.fold(of(none, none)) { a =>
val jsonPayload = toEventJsonPayload(a)
of(jsonPayload.payloadType.some, jsonPayload.payload.some)
}
}
val eventsJsons = events.events.map(eventJson)
val payloadJson = PayloadJson(eventsJsons, events.metadata.some)
withErrorAdapted(events) {
payloadJsonToBytes(payloadJson)
.map(PayloadAndType(_, PayloadType.Json))
}
}
}
private def withErrorAdapted[F[_] : MonadThrowable, A, B](events: Events[A])(fa: F[B]): F[B] =
fa.adaptError { case e =>
JournalError(s"KafkaWrite failed for $events: $e", e)
}
implicit class KafkaWriteOps[F[_], A](val self: KafkaWrite[F, A]) extends AnyVal {
def withMetrics(
metrics: KafkaWriteMetrics[F]
)(
implicit F: Monad[F], measureDuration: MeasureDuration[F]
): KafkaWrite[F, A] = {
events =>
for {
d <- MeasureDuration[F].start
r <- self(events)
d <- d
_ <- metrics(events, r, d)
} yield r
}
def mapK[G[_]](fg: F ~> G): KafkaWrite[G, A] =
(events: Events[A]) => fg(self(events))
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy