queueservice

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Sep 6, 2026 License: MIT Imports: 14 Imported by: 0

README

Queue service lifecycle adapter

queueservice is the target-oriented lifecycle integration between go-queue and go-service. It attaches caller-owned producers and workers to startup, readiness, supervision, drain, and shutdown without choosing a backend or owning retry, acknowledgement, scheduling, or dead-letter policy.

Status and requirements

This is the stable, supported service lifecycle adapter. It requires Go 1.26.6 or later.

Install

go get github.com/faustbrian/go-queue/adapters/service@v1

Quick start

producer, err := queueservice.NewProducer(
	queueservice.ProducerOptions[*queue.Queue]{
		Name:        "orders-producer",
		Resource:    concreteQueue,
		Correlation: correlationFactory,
		Publish:     publish,
	},
)
if err != nil {
	return err
}
runtime, err := service.New(service.Config{
	Components: []service.Component{producer.Component()},
})

Configuration and application callbacks remain caller-owned. The adapter owns admission and lifecycle state; transferred resources are released once through the service shutdown plan. Publish acceptance distinguishes known rejection from unknown outcomes. Callback errors preserve causes without disclosing payloads, endpoints, trace baggage, or credentials.

Use this module only to connect queue resources to go-service. The former github.com/faustbrian/go-queue/queueservice path remains available during successor publication and becomes a deprecated compatibility facade in its following patch release.

See the technical guide, documentation index, migration guide, changelog, security policy, support policy, and MIT license. API documentation is published at pkg.go.dev.

Documentation

Overview

Package queueservice composes explicit queue producers and workers with the service lifecycle and the existing correlation queue semantics.

Producer resources remain concrete and caller-visible. A non-nil Shutdown transfers close ownership; shared resources omit it. Service shutdown first rejects new publishes, waits for active publishes within the service context, and only then closes an owned transport. PublishWithAcceptance exposes definite rejection, confirmed acceptance, and ambiguous acceptance; the adapter never retries an application publish.

LifecycleWorker adapts typed startup, readiness, blocking run, handler, and shutdown callbacks into one service plan. Worker retains the smaller *queue.Queue convenience boundary. Both paths stop intake, join admitted work within the service context, and release the concrete transport only after the drain. NewHandler creates a new request ID for every delivery. TrustedMetadata must be enabled only after authenticating the immediate queue boundary. Callback failures preserve their causes without formatting their text, and callback panics are returned without retaining their values.

Index

Examples

Constants

View Source
const MaxNameBytes = 128

MaxNameBytes bounds component, task, and readiness identifiers.

Variables

View Source
var (
	// ErrInvalidOptions identifies invalid adapter construction.
	ErrInvalidOptions = errors.New("invalid queue service options")
	// ErrUnavailable reports an inactive or draining adapter.
	ErrUnavailable = errors.New("queue service adapter unavailable")
	// ErrMissingCorrelation reports a publish without an explicit parent
	// workflow. Callers beginning new work must start it with their factory.
	ErrMissingCorrelation = errors.New("queue service producer correlation missing")
	// ErrCallbackPanic reports a recovered application callback panic.
	ErrCallbackPanic = errors.New("queue service callback panicked")
	// ErrPublishOutcomeUnknown reports a publish that may have reached the
	// backend and therefore must not be retried blindly.
	ErrPublishOutcomeUnknown = errors.New("queue service publish outcome unknown")
	// ErrInvalidPublishAcceptance reports a callback result outside the public
	// acceptance contract.
	ErrInvalidPublishAcceptance = errors.New("queue service publish acceptance invalid")
	// ErrWorkerExited reports a worker run callback that returned successfully
	// before its context was canceled.
	ErrWorkerExited = errors.New("queue service worker exited unexpectedly")
)

Functions

This section is empty.

Types

type CallbackError

type CallbackError struct {
	// Operation identifies the callback boundary.
	Operation CallbackOperation
	// Err is the original callback failure.
	Err error
}

CallbackError preserves a callback failure for errors.Is and errors.As without formatting potentially sensitive backend or application text.

func (*CallbackError) Error

func (err *CallbackError) Error() string

Error returns a secret-safe callback failure.

func (*CallbackError) Unwrap

func (err *CallbackError) Unwrap() error

Unwrap preserves the callback cause for errors.Is and errors.As.

type CallbackOperation

type CallbackOperation uint8

CallbackOperation identifies one application callback boundary.

const (
	// CallbackStartup identifies resource validation during service start.
	CallbackStartup CallbackOperation = 1
	// CallbackReadiness identifies an opt-in dependency readiness check.
	CallbackReadiness CallbackOperation = 2
	// CallbackPublish identifies concrete producer publication.
	CallbackPublish CallbackOperation = 3
	// CallbackHandler identifies application task handling.
	CallbackHandler CallbackOperation = 4
	// CallbackRun identifies supervised worker intake.
	CallbackRun CallbackOperation = 5
	// CallbackShutdown identifies transferred resource cleanup.
	CallbackShutdown CallbackOperation = 6
	// CallbackAdmission identifies synchronous worker intake closure.
	CallbackAdmission CallbackOperation = 7
)

type CallbackPanicError

type CallbackPanicError struct {
	// Operation identifies the callback boundary.
	Operation CallbackOperation
}

CallbackPanicError identifies a recovered callback without retaining or formatting the panic value.

func (*CallbackPanicError) Error

func (err *CallbackPanicError) Error() string

Error returns a secret-safe callback failure.

func (*CallbackPanicError) Unwrap

func (err *CallbackPanicError) Unwrap() error

Unwrap exposes the stable panic classification.

type Check

type Check[R any] func(context.Context, R) error

Check evaluates whether a resource can accept new work.

type CloseAdmission

type CloseAdmission[R any] func(R) error

CloseAdmission synchronously and idempotently stops new worker intake. It must return promptly and must not wait for admitted handlers to finish.

type Handler

type Handler func(context.Context, core.TaskMessage) error

Handler is the queue worker handler signature.

func NewHandler

func NewHandler(options HandlerOptions) (Handler, error)

NewHandler wraps application work with the existing queue receive boundary.

type HandlerOptions

type HandlerOptions struct {
	// Correlation creates a new request ID for every delivery attempt.
	Correlation *correlation.Factory
	// CorrelationOptions configure the existing queue propagation adapter.
	CorrelationOptions queuecorrelation.Options
	// TrustedMetadata preserves inbound correlation only when explicitly true.
	TrustedMetadata bool
	// TracePropagator explicitly extracts caller-owned telemetry context when
	// configured. Nil disables trace propagation.
	TracePropagator propagation.TextMapPropagator
	// Handler performs application-owned work.
	Handler Handler
}

HandlerOptions configure a correlation-aware delivery boundary.

type LifecycleWorker

type LifecycleWorker[R any] struct {
	// contains filtered or unexported fields
}

LifecycleWorker retains a concrete worker and explicit lifecycle callbacks.

func NewLifecycleWorker

func NewLifecycleWorker[R any](
	options LifecycleWorkerOptions[R],
) (*LifecycleWorker[R], error)

NewLifecycleWorker validates and constructs an inert typed worker adapter.

Example
package main

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/faustbrian/go-correlation"

	queueservice "github.com/faustbrian/go-queue/adapters/service"
	"github.com/faustbrian/go-queue/core"
)

type typedWorkerResource struct{}

type typedDelivery string

func (delivery typedDelivery) Bytes() []byte   { return []byte(delivery) }
func (delivery typedDelivery) Payload() []byte { return []byte(delivery) }

func main() {
	factory, _ := correlation.NewFactory(correlation.FactoryOptions{})
	worker, err := queueservice.NewLifecycleWorker(
		queueservice.LifecycleWorkerOptions[*typedWorkerResource]{
			Name:        "orders-worker",
			Resource:    &typedWorkerResource{},
			Correlation: factory,
			Handler: func(_ context.Context, task core.TaskMessage) error {
				fmt.Println(string(task.Payload()))

				return nil
			},
			Run: func(
				ctx context.Context,
				_ *typedWorkerResource,
				handler queueservice.Handler,
			) error {
				return handler(ctx, typedDelivery("delivery"))
			},
			Shutdown: func(context.Context, *typedWorkerResource) error {
				return nil
			},
		},
	)
	if err != nil {
		return
	}
	plan := worker.Plan()
	if err = plan.Components[0].Start(context.Background()); err != nil {
		return
	}
	if err = plan.Tasks[0].Run(context.Background()); !errors.Is(err, queueservice.ErrWorkerExited) {
		return
	}
	stopContext, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	if err = plan.Components[0].Stop(stopContext); err != nil {
		return
	}

}
Output:
delivery

func (*LifecycleWorker[R]) Plan

func (worker *LifecycleWorker[R]) Plan() service.Plan

Plan returns the worker component, supervised run task, and optional readiness check under one stable identity.

func (*LifecycleWorker[R]) Resource

func (worker *LifecycleWorker[R]) Resource() R

Resource returns the exact caller-provided worker resource.

type LifecycleWorkerOptions

type LifecycleWorkerOptions[R any] struct {
	// Name is the secret-safe component, task, and readiness name.
	Name string
	// Resource is the caller-constructed concrete worker.
	Resource R
	// Correlation creates a new request ID for every delivery attempt.
	Correlation *correlation.Factory
	// CorrelationOptions configure the existing queue propagation adapter.
	CorrelationOptions queuecorrelation.Options
	// TrustedMetadata preserves inbound correlation only when explicitly true.
	TrustedMetadata bool
	// TracePropagator explicitly extracts caller-owned telemetry context when
	// configured. Nil disables trace propagation.
	TracePropagator propagation.TextMapPropagator
	// Handler performs application-owned work.
	Handler Handler
	// Startup optionally validates Resource before intake can run.
	Startup Startup[R]
	// Readiness optionally checks Resource after successful startup.
	Readiness Check[R]
	// CloseAdmission stops backend intake when service drain begins. The adapter
	// independently rejects handler calls after the callback starts.
	CloseAdmission CloseAdmission[R]
	// Run starts intake and joins admitted handlers before returning.
	Run Run[R]
	// Shutdown stops remaining transport work and closes Resource exactly once.
	Shutdown Shutdown[R]
}

LifecycleWorkerOptions configure one typed, supervised worker lifecycle.

type OptionsError

type OptionsError struct {
	// Field identifies the rejected option.
	Field string
	// Reason describes the safe failure category.
	Reason string
}

OptionsError identifies one rejected option.

func (*OptionsError) Error

func (err *OptionsError) Error() string

Error returns a secret-safe construction diagnostic.

func (*OptionsError) Unwrap

func (err *OptionsError) Unwrap() error

Unwrap exposes the stable option classification.

type Producer

type Producer[R any] struct {
	// contains filtered or unexported fields
}

Producer retains a concrete producer and coordinates its in-flight calls.

func NewProducer

func NewProducer[R any](options ProducerOptions[R]) (*Producer[R], error)

NewProducer validates and constructs an inert producer adapter.

func (*Producer[R]) Component

func (producer *Producer[R]) Component() service.Component

Component returns the producer's ordered service lifecycle component.

func (*Producer[R]) Publish

func (producer *Producer[R]) Publish(
	ctx context.Context,
	message core.QueuedMessage,
	options ...job.AllowOption,
) (correlation.Values, error)

Publish creates a message hop, attaches its carrier to cloned job metadata, and invokes the concrete producer with that child correlation in its context. Correlation values are also returned for caller-owned logging and telemetry.

func (*Producer[R]) PublishWithAcceptance

func (producer *Producer[R]) PublishWithAcceptance(
	ctx context.Context,
	message core.QueuedMessage,
	options ...job.AllowOption,
) (correlation.Values, PublishAcceptance, error)

PublishWithAcceptance creates a message hop and reports whether the concrete backend accepted the task. The adapter performs exactly one callback call and never retries an unknown result.

func (*Producer[R]) Readiness

func (producer *Producer[R]) Readiness() (service.ReadinessCheck, bool)

Readiness returns the configured opt-in dependency check.

func (*Producer[R]) Resource

func (producer *Producer[R]) Resource() R

Resource returns the exact caller-provided producer.

type ProducerOptions

type ProducerOptions[R any] struct {
	// Name is the secret-safe component name.
	Name string
	// Resource is the caller-constructed concrete producer.
	Resource R
	// Correlation creates message-hop identifiers.
	Correlation *correlation.Factory
	// CorrelationOptions configure the existing queue propagation adapter.
	CorrelationOptions queuecorrelation.Options
	// TracePropagator explicitly injects caller-owned telemetry context when
	// configured. Nil disables trace propagation.
	TracePropagator propagation.TextMapPropagator
	// Startup optionally validates Resource before admission begins.
	Startup Startup[R]
	// Readiness optionally checks Resource after successful startup.
	Readiness Check[R]
	// Publish performs one concrete, caller-bounded append. A returned error has
	// unknown backend acceptance. Prefer PublishWithAcceptance when the backend
	// can distinguish a definite rejection from an ambiguous result.
	Publish Publish[R]
	// PublishWithAcceptance performs one concrete append with an explicit
	// backend-acceptance result. Exactly one publish callback is required.
	PublishWithAcceptance PublishWithAcceptance[R]
	// Shutdown transfers transport close ownership when non-nil.
	Shutdown Shutdown[R]
}

ProducerOptions configure one producer lifecycle adapter.

type Publish

type Publish[R any] func(
	context.Context,
	R,
	core.QueuedMessage,
	...job.AllowOption,
) error

Publish appends one correlation-aware message through a concrete resource.

type PublishAcceptance

type PublishAcceptance uint8

PublishAcceptance reports whether a failed publish reached the backend.

const (
	// PublishNotAccepted means the backend definitively did not accept the task.
	PublishNotAccepted PublishAcceptance = 1
	// PublishAccepted means the backend definitively accepted the task.
	PublishAccepted PublishAcceptance = 2
	// PublishUnknown means the backend may have accepted the task. Applications
	// must reconcile or rely on idempotency instead of retrying blindly.
	PublishUnknown PublishAcceptance = 3
)

type PublishError

type PublishError struct {
	// Acceptance describes whether the task reached the backend.
	Acceptance PublishAcceptance
	// Err is the original classifiable failure.
	Err error
}

PublishError preserves the backend cause and acceptance classification without formatting potentially sensitive backend details.

func (*PublishError) Error

func (err *PublishError) Error() string

Error returns a secret-safe publish diagnostic.

func (*PublishError) Unwrap

func (err *PublishError) Unwrap() error

Unwrap preserves the backend and stable acceptance causes.

type PublishWithAcceptance

type PublishWithAcceptance[R any] func(
	context.Context,
	R,
	core.QueuedMessage,
	...job.AllowOption,
) (PublishAcceptance, error)

PublishWithAcceptance appends one task and reports whether a failure reached the backend. It must not retry application work internally.

type Run

type Run[R any] func(context.Context, R, Handler) error

Run owns worker intake until cancellation or backend failure and must join every handler it admits before returning.

type Shutdown

type Shutdown[R any] func(context.Context, R) error

Shutdown releases an explicitly transferred producer resource.

type Startup

type Startup[R any] func(context.Context, R) error

Startup validates an explicitly constructed resource before admission.

type StartupError

type StartupError struct {
	// Validation is the startup-check failure.
	Validation error
	// Cleanup is an optional transferred-resource shutdown failure.
	Cleanup error
}

StartupError preserves validation and partial-cleanup failures without formatting either potentially sensitive cause.

func (*StartupError) Error

func (err *StartupError) Error() string

Error returns a secret-safe startup diagnostic.

func (*StartupError) Unwrap

func (err *StartupError) Unwrap() []error

Unwrap preserves both causes for errors.Is and errors.As.

type Worker

type Worker struct {
	// contains filtered or unexported fields
}

Worker retains one concrete queue.

func NewWorker

func NewWorker(options WorkerOptions) (*Worker, error)

NewWorker validates and constructs an inert worker lifecycle adapter.

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/faustbrian/go-correlation"
	queue "github.com/faustbrian/go-queue"

	queueservice "github.com/faustbrian/go-queue/adapters/service"
	"github.com/faustbrian/go-queue/core"
	"github.com/faustbrian/go-queue/job"
	"go.opentelemetry.io/otel/propagation"
)

type payload string

func (value payload) Bytes() []byte { return []byte(value) }

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	factory, _ := correlation.NewFactory(correlation.FactoryOptions{})
	handled := make(chan string, 1)
	handler, err := queueservice.NewHandler(queueservice.HandlerOptions{
		Correlation:     factory,
		TrustedMetadata: true,
		TracePropagator: propagation.TraceContext{},
		Handler: func(_ context.Context, task core.TaskMessage) error {
			handled <- string(task.Payload())

			return nil
		},
	})
	if err != nil {
		return
	}
	ring := queue.NewRing(queue.WithFn(handler))
	concrete, err := queue.NewQueue(
		queue.WithWorker(ring),
		queue.WithWorkerCount(1),
	)
	if err != nil {
		return
	}
	worker, err := queueservice.NewWorker(queueservice.WorkerOptions{
		Name: "jobs-worker", Queue: concrete,
	})
	if err != nil {
		return
	}
	producer, err := queueservice.NewProducer(
		queueservice.ProducerOptions[*queue.Queue]{
			Name: "jobs-producer", Resource: concrete, Correlation: factory,
			TracePropagator: propagation.TraceContext{},
			Publish: func(
				_ context.Context,
				resource *queue.Queue,
				message core.QueuedMessage,
				options ...job.AllowOption,
			) error {
				return resource.Queue(message, options...)
			},
		},
	)
	if err != nil {
		return
	}

	workerComponent := worker.Component()
	producerComponent := producer.Component()
	if err = workerComponent.Start(ctx); err != nil {
		return
	}
	if err = producerComponent.Start(ctx); err != nil {
		return
	}
	parent, _ := factory.Start()
	if _, err = producer.Publish(
		correlation.WithValues(ctx, parent),
		payload("delivery"),
	); err != nil {
		return
	}
	select {
	case value := <-handled:
		fmt.Println(value)
	case <-ctx.Done():
		return
	}
	if err = producerComponent.Stop(ctx); err != nil {
		return
	}
	if err = workerComponent.Stop(ctx); err != nil {
		return
	}

}
Output:
delivery

func (*Worker) Component

func (worker *Worker) Component() service.Component

Component starts the queue and gracefully releases it during service stop.

func (*Worker) Queue

func (worker *Worker) Queue() *queue.Queue

Queue returns the exact caller-provided queue.

type WorkerOptions

type WorkerOptions struct {
	// Name is the secret-safe component name.
	Name string
	// Queue is the caller-constructed concrete queue.
	Queue *queue.Queue
}

WorkerOptions configure one concrete queue worker lifecycle.

Jump to

Keyboard shortcuts

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