babelqueue

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 6, 2026 License: MIT Imports: 7 Imported by: 10

README

BabelQueue for Go

CI Go Reference Go Report Card

Polyglot Queues, Simplified. Read and write the canonical BabelQueue message envelope from Go — so your Go services exchange messages with Laravel, Symfony, Python, .NET and Node over one strict JSON format, on the broker you already run.

This is the framework-agnostic Go core: the wire-envelope codec, contracts and dead-letter helpers — zero dependencies (standard library only). The full standard is documented at babelqueue.com.

Installation

go get github.com/babelqueue/babelqueue-go

Requires Go >=1.21.

Usage

package main

import (
	"fmt"

	babelqueue "github.com/babelqueue/babelqueue-go"
)

func main() {
	// Produce — build the canonical envelope and publish the JSON to your broker.
	env, err := babelqueue.Make(
		"urn:babel:orders:created",
		map[string]any{"order_id": 1042},
		babelqueue.WithQueue("orders"),
	)
	if err != nil {
		panic(err)
	}
	body, _ := env.Encode() // []byte of compact UTF-8 JSON
	// redisClient.RPush(ctx, "queues:orders", body)
	//   /  ch.PublishWithContext(ctx, "", "orders", false, false, amqp.Publishing{Body: body})

	// Consume — decode a message produced by ANY BabelQueue SDK.
	in, err := babelqueue.Decode(body)
	if err != nil || !in.Accepts() {
		return // malformed or unsupported — dead-letter / drop
	}
	switch in.URN() {
	case "urn:babel:orders:created":
		fmt.Println(in.Data["order_id"], in.TraceID) // 1042, the cross-service trace id
	}
}

The envelope is identical to every other SDK's:

{
  "job": "urn:babel:orders:created",
  "trace_id": "…",
  "data": { "order_id": 1042 },
  "meta": { "id": "…", "queue": "orders", "lang": "go", "schema_version": 1, "created_at": 1749132727000 },
  "attempts": 0
}

Encode disables HTML escaping and emits compact JSON — the same canonical wire form as the PHP and Python cores (the envelope frame is byte-identical). Key order inside your data map follows encoding/json (sorted), where PHP/Python keep insertion order — JSON object key order is insignificant, so consumers read them identically. JSON numbers decode into Data as float64 (encoding/json's default for any).

Trace continuation

Make mints a fresh trace_id unless you pass one — propagate an inbound trace across a hop with WithTraceID:

next, _ := babelqueue.Make(
	"urn:babel:shipping:requested",
	map[string]any{"order_id": 1042},
	babelqueue.WithTraceID(in.TraceID),
)
Dead-letter
dl := babelqueue.Annotate(env, "failed", "orders", 3, err) // additive dead_letter block
body, _ := dl.Encode()
// publish body to the "orders.dlq" queue

Annotate returns a copy — the original envelope is preserved unchanged inside the dead-lettered message, so any-language consumers can still read it.

What this core is (and isn't)

It enforces the contract: the envelope shape, URN identity, trace propagation, schema-version gating and the dead-letter block. It is intentionally not a worker/runtime — retry loops, acks and broker wiring stay in your own code (or a future thin adapter), exactly as with the other SDK cores.

Unknown-URN strategy constants (StrategyFail, StrategyDelete, StrategyRelease, StrategyDeadLetter) are provided for adapters to act on.

Conformance

This core passes the shared cross-SDK conformance suite (vendored under testdata/conformance/) — the same fixtures every BabelQueue SDK must satisfy, so a Go producer and, say, a Laravel consumer agree byte-for-byte.

go test ./...

License

MIT © Muhammet Şafak

Documentation

Overview

Package babelqueue is the framework-agnostic Go core of BabelQueue — "Polyglot Queues, Simplified".

It implements the canonical wire envelope so a Go service interoperates byte-for-byte with the PHP/Laravel, Python, ... SDKs over any broker, with no language-specific serialization on the wire:

env, _ := babelqueue.Make("urn:babel:orders:created", map[string]any{"order_id": 1042})
body, _ := env.Encode() // publish body to Redis / RabbitMQ / ...

in, _ := babelqueue.Decode(body)
if in.Accepts() {
	switch in.URN() {
	case "urn:babel:orders:created":
		// handle in.Data ...
	}
}

The package depends only on the standard library.

Full spec: https://babelqueue.com

Index

Constants

View Source
const (
	StrategyFail       = "fail"        // surface an error; let the worker decide
	StrategyDelete     = "delete"      // drop the message
	StrategyRelease    = "release"     // requeue for another consumer
	StrategyDeadLetter = "dead_letter" // route to the dead-letter queue
)

Unknown-URN strategies: what a consumer does with a message whose URN has no registered handler. These mirror the constants in every other SDK core.

View Source
const SchemaVersion = 1

SchemaVersion is the wire envelope schema version this core implements. It is versioned independently of the module's release (package) version: the wire format only changes when this number does.

View Source
const SourceLang = "go"

SourceLang is stamped into meta.lang for envelopes produced by this core.

Variables

View Source
var (
	// ErrEmptyURN is returned by Make when the URN is blank — a polyglot message
	// must expose a stable, non-empty URN so consumers can identify it without
	// relying on any language-specific class name.
	ErrEmptyURN = errors.New("babelqueue: a polyglot message must expose a stable, non-empty URN")

	// ErrUnknownURN signals that no handler is mapped for a message URN.
	ErrUnknownURN = errors.New("babelqueue: no handler is mapped for the message URN")
)

Sentinel errors returned by the core.

Functions

This section is empty.

Types

type DeadLetter

type DeadLetter struct {
	Reason        string  `json:"reason"`
	Error         *string `json:"error"`     // null when no cause was supplied
	Exception     *string `json:"exception"` // null when no cause was supplied
	FailedAt      int64   `json:"failed_at"` // Unix milliseconds, UTC
	OriginalQueue string  `json:"original_queue"`
	Attempts      int     `json:"attempts"`
	Lang          string  `json:"lang"`
}

DeadLetter is the additive block appended to an envelope when a message is dead-lettered. The original envelope is preserved unchanged alongside it, so a consumer in any language can still read the original job, data and trace_id.

type Envelope

type Envelope struct {
	Job        string         `json:"job"`      // the message URN (never a class name)
	TraceID    string         `json:"trace_id"` // correlation id, preserved across hops
	Data       map[string]any `json:"data"`     // the message payload
	Meta       Meta           `json:"meta"`
	Attempts   int            `json:"attempts"`              // top-level transport retry counter
	DeadLetter *DeadLetter    `json:"dead_letter,omitempty"` // present only once dead-lettered
}

Envelope is the canonical BabelQueue wire message: a strict, language-neutral JSON shape ({job, trace_id, data, meta, attempts}) that every SDK produces and consumes identically. The field order below is significant — it matches the other cores so the envelope frame encodes identically across languages. (Key order inside the user-supplied Data map is JSON-insignificant; encoding/json emits it sorted, where PHP/Python preserve insertion order — same JSON.)

func Annotate

func Annotate(env Envelope, reason, originalQueue string, attempts int, cause error) Envelope

Annotate returns a copy of the envelope with a dead_letter block attached, recording why and where it failed; it does not mutate the original. A non-nil cause fills in the error message and its Go type; both serialize as JSON null when cause is nil.

func Decode

func Decode(raw []byte) (Envelope, error)

Decode parses a raw JSON body into an Envelope. It accepts "urn" as an inbound alias for "job" (resolving it into Job). It does not validate the contents — use Accepts for consumer-side validation.

func Make

func Make(urn string, data map[string]any, opts ...Option) (Envelope, error)

Make builds the canonical envelope for a (urn, data) pair. It mints a fresh trace id unless WithTraceID is given, starts attempts at 0, and stamps meta with a unique id, the source language ("go"), the schema version and a millisecond timestamp. It returns ErrEmptyURN when urn is blank.

func (Envelope) Accepts

func (e Envelope) Accepts() bool

Accepts reports whether a consumer should accept this envelope. It rejects a missing URN, an unsupported meta.schema_version, a blank trace_id, or missing data — the consumer-side counterpart to the producer JSON Schema. (It accepts the urn alias, unlike the stricter producer schema.)

func (Envelope) Encode

func (e Envelope) Encode() ([]byte, error)

Encode renders the envelope as compact UTF-8 JSON with HTML escaping disabled (unescaped slashes and unicode) — the canonical wire form shared by every SDK. The envelope frame is identical across languages; key order within the Data map follows encoding/json (sorted), which is semantically the same JSON.

func (Envelope) URN

func (e Envelope) URN() string

URN returns the message URN — the canonical job, with the urn alias already resolved by Decode.

type Meta

type Meta struct {
	ID            string `json:"id"`
	Queue         string `json:"queue"`
	Lang          string `json:"lang"`
	SchemaVersion int    `json:"schema_version"`
	CreatedAt     int64  `json:"created_at"` // Unix milliseconds, UTC
}

Meta is the immutable per-message metadata block.

type Option

type Option func(*makeConfig)

Option customizes Make.

func WithQueue

func WithQueue(queue string) Option

WithQueue sets the logical queue name recorded in meta.queue (default "default").

func WithTraceID

func WithTraceID(traceID string) Option

WithTraceID reuses an existing trace id (trace continuation) instead of minting a fresh one. A blank value is ignored.

Directories

Path Synopsis
amqp module
artemis module
asynq module
kafka module
machinery module
otel module
pulsar module
redis module
sqs module

Jump to

Keyboard shortcuts

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