Help us shape the future of Go! Take the 2026 Go Developer Survey

consumer

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 26, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package consumer runs a typed Kafka consumer loop around a user-supplied handler.

A Handler declares the topic it reads, the consumer group it joins, how to process a message of type T, and its retry policy. New wires it to a client factory and a dispatcher:

c, err := consumer.New[OrderPlaced](d, factory, handler)
if err != nil {
	return err
}

shutdown, err := c.Run()

Run starts the poll loop in the background and returns immediately. Calling the returned Shutdown drains the in-flight message and closes the client. A failure inside the loop - a broker error, a commit error - is reported on the channel from ListenShutdown, and the loop stops.

Offsets are committed by this package, not by librdkafka: the client factory disables auto-commit, and a message is committed only after the handler returns without error.

When the handler fails, the error is matched against the RetryableErrors of its ConfigRetry. A retryable error is retried with exponential backoff; a non-retryable one, or a retry sequence that exhausts MaxElapsedTime, sends the message to the dead letter topic, which is the source topic suffixed with "-dlt". Messages whose payload cannot be decoded at all go to the same topic as plain text.

Each message gets its own logger, carrying the consumer group, the topic, the partition, the offset and a freshly generated trace id. The handler reads it with log.FromContext.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

type Client interface {
	Subscribe(topic string, cb kafka.RebalanceCb) error
	ReadMessage(timeout time.Duration) (*kafka.Message, error)
	CommitMessage(m *kafka.Message) ([]kafka.TopicPartition, error)
	Close() error
}

Client is the Kafka consumer driver a Consumer polls. It is the subset of the confluent client the loop uses, which is what makes the loop testable.

type ClientOption

type ClientOption func(s clientSettings) clientSettings

ClientOption configures the Kafka client built by NewFactory.

func WithBootstrapServer

func WithBootstrapServer(servers string) ClientOption

WithBootstrapServer sets the broker list, as a comma-separated "host:port" string.

type ConfigRetry

type ConfigRetry struct {
	RetryableErrors []error
	InitialInterval time.Duration
	MaxElapsedTime  time.Duration
	MaxInterval     time.Duration
}

ConfigRetry is a handler retry policy.

Only errors matching RetryableErrors under errors.Is are retried; anything else goes straight to the dead letter topic. The remaining fields configure the exponential backoff, and a retry sequence that exceeds MaxElapsedTime is abandoned to the dead letter topic as well.

The zero value retries nothing. Build one with NewConfigRetry or as a literal.

func NewConfigRetry

func NewConfigRetry(options ...ConfigRetryOption) ConfigRetry

NewConfigRetry builds a ConfigRetry from options.

type ConfigRetryOption

type ConfigRetryOption func(retrySettings configRetrySettings) configRetrySettings

ConfigRetryOption configures a ConfigRetry built by NewConfigRetry.

func WithInitialInterval

func WithInitialInterval(initialInterval time.Duration) ConfigRetryOption

WithInitialInterval sets the delay before the first retry.

func WithMaxElapsedTime

func WithMaxElapsedTime(maxElapsedTime time.Duration) ConfigRetryOption

WithMaxElapsedTime caps how long retrying may go on before the message is sent to the dead letter topic.

The consumer does not poll while retrying, so a value beyond the broker session timeout gets the consumer evicted from its group.

func WithMaxInterval

func WithMaxInterval(maxInterval time.Duration) ConfigRetryOption

WithMaxInterval caps the delay between retries as the backoff grows.

func WithRetryableErrors

func WithRetryableErrors(errs ...error) ConfigRetryOption

WithRetryableErrors sets the errors worth retrying. They are matched with errors.Is, so sentinels survive wrapping.

type Consumer

type Consumer interface {
	Run() (Shutdown, error)
	ListenShutdown() <-chan error
}

Consumer is a running Kafka consumer loop.

func New

func New[T any](
	dispatcher dispatcher.Dispatcher,
	factory Factory,
	handler Handler[T],
	options ...Option,
) (Consumer, error)

New builds a Consumer for messages decoded into T.

It creates a Kafka client through factory using the handler ID as the group, so it fails if the broker is unreachable. dispatcher is used to publish to the dead letter topic, and is therefore required even for a consumer that never fails.

New does not start anything - call Run.

Example

Requires a Kafka broker, so this example is compiled but not run.

package main

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/diegodesousas/go-devkit/pkg/log"
	"github.com/diegodesousas/go-devkit/pkg/stream/consumer"
	"github.com/diegodesousas/go-devkit/pkg/stream/dispatcher"
	"github.com/pkg/errors"
)

type orderPlaced struct {
	ID    string `json:"id"`
	Total int    `json:"total"`
}

// errChargeUnavailable stands for a downstream failure worth retrying, as
// opposed to a malformed message, which is not.
var errChargeUnavailable = errors.New("charge service unavailable")

type orderHandler struct{}

func (orderHandler) ID() string    { return "billing" }
func (orderHandler) Topic() string { return "orders" }

// ShouldSkip runs before Handle and acknowledges the message without processing
// it, which is how a consumer ignores traffic it does not care about.
func (orderHandler) ShouldSkip(o orderPlaced) bool { return o.Total == 0 }

func (orderHandler) Handle(ctx context.Context, o orderPlaced) error {
	log.Info(ctx, "charging order", log.NewField("order-id", o.ID))
	return nil
}

// Only errors listed here are retried; anything else goes straight to the dead
// letter topic.
func (orderHandler) ConfigRetry() consumer.ConfigRetry {
	return consumer.NewConfigRetry(
		consumer.WithRetryableErrors(errChargeUnavailable),
		consumer.WithInitialInterval(time.Second),
		consumer.WithMaxInterval(5*time.Second),
		consumer.WithMaxElapsedTime(30*time.Second),
	)
}

// Requires a Kafka broker, so this example is compiled but not run.
func main() {
	client, err := dispatcher.NewClient(dispatcher.WithBootstrapServers("localhost:9092"))
	if err != nil {
		panic(err)
	}

	// The dispatcher is what publishes to the dead letter topic, so a consumer
	// needs one even when the handler never fails.
	d := dispatcher.New(client)
	defer d.Shutdown()

	factory := consumer.NewFactory(consumer.WithBootstrapServer("localhost:9092"))

	c, err := consumer.New[orderPlaced](d, factory, orderHandler{})
	if err != nil {
		panic(err)
	}

	shutdown, err := c.Run() // returns immediately; the loop runs in the background
	if err != nil {
		panic(err)
	}

	interrupt := make(chan os.Signal, 1)
	signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)

	// The loop reports its own failures here, so a broker error stops the
	// process rather than leaving it running and consuming nothing.
	go func() {
		if err := <-c.ListenShutdown(); err != nil {
			fmt.Println("consumer stopped:", err)
			interrupt <- syscall.SIGTERM
		}
	}()

	<-interrupt

	shutdown() // waits for the in-flight message, then closes the client
}

type Factory

type Factory interface {
	New(groupID string) (Client, error)
}

Factory builds a Client for a consumer group.

func NewFactory

func NewFactory(options ...ClientOption) Factory

NewFactory returns a Factory producing confluent Kafka consumers.

Auto-commit is off: offsets are committed by the Consumer once a handler succeeds. Session timeout is 45s and the group starts from the earliest offset. Only the bootstrap servers are configurable, so a cluster requiring SASL or TLS cannot be reached.

type Handler

type Handler[T any] interface {
	ID() string
	Topic() string
	ShouldSkip(content T) bool
	Handle(ctx context.Context, content T) error
	ConfigRetry() ConfigRetry
}

Handler is the user code a Consumer drives.

ID names the consumer group, Topic the topic to read. ShouldSkip is consulted before Handle and lets a message be acknowledged without processing. ConfigRetry declares which errors are worth retrying and how long to keep trying; it is consulted on every failure, so it must be cheap.

Handle receives a context carrying a logger scoped to the message. Returning nil commits the offset.

type Option

type Option func(s settings) settings

Option configures a Consumer built by New.

func WithLogger

func WithLogger(logger log.Logger) Option

WithLogger sets the base logger. The Consumer derives from it per message, adding the group, topic, partition, offset and a trace id. Defaults to log.New().

func WithStringGenerator

func WithStringGenerator(generator gen.StringGenerator) Option

WithStringGenerator sets the source of per-message trace ids. Defaults to gen.UUIDGenerator; inject gen.SequenceGenerator in tests for deterministic output.

type Shutdown

type Shutdown func()

Shutdown stops the consumer: it waits for the message being processed, then closes the Kafka client.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL