kafka-effectful-0.2.0.0: src/Kafka/Effectful/Producer/Effect.hs
module Kafka.Effectful.Producer.Effect (
-- * Effect
KafkaProducer (..),
-- * Operations
produceMessage,
produceMessage',
produceMessageSync,
produceMessageBatch,
flushProducer,
-- * Transactions
initTransactions,
beginTransaction,
commitTransaction,
abortTransaction,
-- ** Internal — cross-effect plumbing
sendOffsetsToTransaction,
askProducerHandle,
)
where
import Effectful (Dispatch (..), DispatchOf, Eff, Effect, (:>))
import Effectful.Dispatch.Dynamic (send)
import Kafka.Consumer.Types (ConsumerRecord, Offset)
import Kafka.Consumer.Types qualified as KC
import Kafka.Producer.Types (DeliveryReport, ProducerRecord)
import Kafka.Producer.Types qualified as KP
import Kafka.Transaction (TxError)
import Kafka.Types (KafkaError, Timeout)
-- | Effect for Kafka producer operations.
data KafkaProducer :: Effect where
ProduceMessage ::
ProducerRecord ->
KafkaProducer m ()
ProduceMessage' ::
ProducerRecord ->
(DeliveryReport -> IO ()) ->
KafkaProducer m ()
ProduceMessageSync ::
ProducerRecord ->
KafkaProducer m Offset
ProduceMessageBatch ::
[ProducerRecord] ->
KafkaProducer m [(ProducerRecord, KafkaError)]
FlushProducer ::
KafkaProducer m ()
InitTransactions ::
Timeout ->
KafkaProducer m ()
BeginTransaction ::
KafkaProducer m ()
CommitTransaction ::
Timeout ->
KafkaProducer m (Maybe TxError)
AbortTransaction ::
Timeout ->
KafkaProducer m ()
SendOffsetsToTransaction ::
KC.KafkaConsumer ->
ConsumerRecord k v ->
Timeout ->
KafkaProducer m (Maybe TxError)
AskProducerHandle ::
KafkaProducer m KP.KafkaProducer
type instance DispatchOf KafkaProducer = 'Dynamic
{- | Send a single message to Kafka.
Throws 'KafkaError' via the 'Error' effect on failure.
-}
produceMessage :: (KafkaProducer :> es) => ProducerRecord -> Eff es ()
produceMessage = send . ProduceMessage
{- | Send a single message with a per-message 'DeliveryReport' callback.
The callback runs on a librdkafka-forked thread, so blocking operations
(such as writing to an @MVar@) are safe. Throws 'KafkaError' via the
'Error' effect when the underlying send fails to enqueue
(@ImmediateError@).
This is the low-level primitive for Scenario 2 of
@hw-kafka-client@'s producer best practices. Callers that only need a
single synchronous send should prefer 'produceMessageSync'.
@since 0.2.0.0
-}
produceMessage' ::
(KafkaProducer :> es) =>
ProducerRecord ->
(DeliveryReport -> IO ()) ->
Eff es ()
produceMessage' record cb = send (ProduceMessage' record cb)
{- | Send a single message and block until the broker acknowledges it,
returning the broker-assigned 'Offset'.
Throws 'KafkaError' via the 'Error' effect on enqueue failure
(@ImmediateError@) or on delivery failure reported via the
'DeliveryReport'.
This is the high-level convenience for Scenario 2 of
@hw-kafka-client@'s producer best practices.
@since 0.2.0.0
-}
produceMessageSync ::
(KafkaProducer :> es) =>
ProducerRecord ->
Eff es Offset
produceMessageSync = send . ProduceMessageSync
{- | Send many records in one call.
Returns only the records that failed to enqueue, paired with the
error librdkafka reported for that record. Successful records are
omitted. This mirrors @Kafka.Producer.produceMessageBatch@.
Combined with @linger.ms@ and @batch.size@ set on the
@ProducerProperties@, this is the throughput-oriented path — Scenario
4 of @hw-kafka-client@'s producer best practices.
@since 0.2.0.0
-}
produceMessageBatch ::
(KafkaProducer :> es) =>
[ProducerRecord] ->
Eff es [(ProducerRecord, KafkaError)]
produceMessageBatch = send . ProduceMessageBatch
-- | Flush the producer's outbound queue, blocking until all messages are sent.
flushProducer :: (KafkaProducer :> es) => Eff es ()
flushProducer = send FlushProducer
{- | Initialise the transactional producer.
Must be called exactly once per producer, after @runKafkaProducer@
has acquired the handle and before any call to 'beginTransaction'.
The producer's @ProducerProperties@ must set @transactional.id@,
@enable.idempotence=true@, and @acks=all@.
Throws 'KafkaError' via the 'Error' effect on failure.
@since 0.2.0.0
-}
initTransactions :: (KafkaProducer :> es) => Timeout -> Eff es ()
initTransactions = send . InitTransactions
{- | Open a new transaction.
Must be preceded by exactly one successful 'initTransactions' on the
same producer handle. Throws 'KafkaError' via the 'Error' effect on
failure.
@since 0.2.0.0
-}
beginTransaction :: (KafkaProducer :> es) => Eff es ()
beginTransaction = send BeginTransaction
{- | Commit the currently-open transaction.
Returns @Nothing@ on success or @Just TxError@ on failure. The
caller must branch on the three 'TxError' discriminators
(@kafkaErrorTxnRequiresAbort@ first, then @kafkaErrorIsRetriable@,
then @kafkaErrorIsFatal@) to decide whether to abort, retry, or
crash.
@since 0.2.0.0
-}
commitTransaction :: (KafkaProducer :> es) => Timeout -> Eff es (Maybe TxError)
commitTransaction = send . CommitTransaction
{- | Abort the currently-open transaction.
Throws 'KafkaError' via the 'Error' effect on failure.
@since 0.2.0.0
-}
abortTransaction :: (KafkaProducer :> es) => Timeout -> Eff es ()
abortTransaction = send . AbortTransaction
{- | Send the consumer offsets for a single 'ConsumerRecord' to the
open transaction.
This is plumbing used by
'Kafka.Effectful.Producer.Transaction.commitOffsetMessageTransaction'
and is not intended to be called directly — end-users should use the
helper, which picks up the consumer handle automatically via
@askConsumerHandle@.
@since 0.2.0.0
-}
sendOffsetsToTransaction ::
(KafkaProducer :> es) =>
KC.KafkaConsumer ->
ConsumerRecord k v ->
Timeout ->
Eff es (Maybe TxError)
sendOffsetsToTransaction consumer record timeout =
send (SendOffsetsToTransaction consumer record timeout)
{- | Escape hatch: return the raw @Kafka.Producer.KafkaProducer@ handle
acquired by @runKafkaProducer@.
Exposed to enable the cross-effect
'Kafka.Effectful.Producer.Transaction.commitOffsetMessageTransaction'
helper, which must reach both the producer and consumer handles to
call the underlying transactional offset-commit primitive. New
operations should go through the 'KafkaProducer' effect rather than
this handle.
@since 0.2.0.0
-}
askProducerHandle :: (KafkaProducer :> es) => Eff es KP.KafkaProducer
askProducerHandle = send AskProducerHandle