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

com.snowplowanalytics.snowplow.sinks.pubsub.PubsubSink.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2023-present Snowplow Analytics Ltd. All rights reserved.
 *
 * This program is licensed to you under the Snowplow Community License Version 1.0,
 * and you may not use this file except in compliance with the Snowplow Community License Version 1.0.
 * You may obtain a copy of the Snowplow Community License Version 1.0 at https://docs.snowplow.io/community-license-1.0
 */
package com.snowplowanalytics.snowplow.sinks.pubsub

import cats.effect.{Async, Sync}
import cats.effect.kernel.Resource
import cats.implicits._
import cats.Foldable
import com.google.api.core.{ApiFuture, ApiFutures}
import com.google.api.gax.batching.BatchingSettings
import com.google.cloud.pubsub.v1.Publisher
import com.google.protobuf.UnsafeSnowplowOps
import com.google.pubsub.v1.{ProjectTopicName, PubsubMessage}
import com.snowplowanalytics.snowplow.pubsub.FutureInterop
import com.snowplowanalytics.snowplow.sinks.{ListOfList, Sink, Sinkable}
import org.threeten.bp.{Duration => ThreetenDuration}

import scala.jdk.CollectionConverters._
import java.util.UUID

import com.snowplowanalytics.snowplow.pubsub.GcpUserAgent

object PubsubSink {

  def resource[F[_]: Async](config: PubsubSinkConfig): Resource[F, Sink[F]] =
    mkPublisher[F](config).map { p =>
      Sink(sinkBatch[F](p, _))
    }

  private def sinkBatch[F[_]: Async](publisher: Publisher, batch: ListOfList[Sinkable]): F[Unit] =
    Foldable[ListOfList]
      .foldM(batch, List.empty[ApiFuture[String]]) { case (futures, Sinkable(bytes, _, attributes)) =>
        for {
          uuid <- Async[F].delay(UUID.randomUUID)
          message = PubsubMessage.newBuilder
                      .setData(UnsafeSnowplowOps.wrapBytes(bytes))
                      .setMessageId(uuid.toString)
                      .putAllAttributes(attributes.asJava)
                      .build
          fut <- Async[F].delay(publisher.publish(message))
        } yield fut :: futures
      }
      .flatMap { futures =>
        for {
          _ <- Async[F].delay(publisher.publishAllOutstanding)
          combined = ApiFutures.allAsList(futures.asJava)
          _ <- FutureInterop.fromFuture(combined)
        } yield ()
      }

  private def mkPublisher[F[_]: Sync](config: PubsubSinkConfig): Resource[F, Publisher] = {
    val topic = ProjectTopicName.of(config.topic.projectId, config.topic.topicId)

    val batchSettings = BatchingSettings.newBuilder
      .setElementCountThreshold(config.batchSize)
      .setRequestByteThreshold(config.requestByteThreshold)
      .setDelayThreshold(ThreetenDuration.ofNanos(Long.MaxValue))

    val make = Sync[F].delay {
      Publisher
        .newBuilder(topic)
        .setBatchingSettings(batchSettings.build)
        .setHeaderProvider(GcpUserAgent.headerProvider(config.gcpUserAgent))
        .build
    }

    Resource.make(make) { publisher =>
      Sync[F].blocking {
        publisher.shutdown()
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy