Skip to content

Send a CloudEvent over NATS

Publishing

nats.Header is defined as map[string][]string, the same type as envelope.Wire.Headers, so this is a type conversion rather than a copy. NATS has no native content type, so Wire.ContentType goes in as the content-type header:

format := cloudevents.Binary(cloudevents.NATS)

w, err := format.Encode(e)
if err != nil {
    return err
}

header := nats.Header(w.Headers)
if header == nil {
    header = nats.Header{}
}

if w.ContentType != "" {
    header["content-type"] = []string{w.ContentType}
}

err = client.PublishMsg(ctx, &nats.Msg{
    Subject: "orders.created",
    Header:  header,
    Data:    w.Body,
})

The nil check is for Structured(), which writes no attribute headers. Binary always returns a map.

Use go/nats's client rather than a bare nats.Conn. These modules have no opinion about which you use, but that one is where the bounded subscriptions and the shed reporting live.

Subscribing

sub, err := client.Subscribe("orders.created", func(msg *nats.Msg) {
    e, _, err := format.Decode(envelope.Wire{Headers: msg.Header, Body: msg.Data})
    if err != nil {
        refused.Add(1)
        logger.Error("refused a CloudEvent", "id", e.ID, "err", err)

        return
    }

    handle(e)
})

Decode takes the content type from the content-type header when Wire.ContentType is empty, whatever the header's case. To accept binary and structured mode on one subject, use envelope.Read instead: Read and write more than one format.

Route on the type, not on the subject

A NATS subject and a CloudEvent type say overlapping things, and the temptation is to encode the type into the subject and stop setting the attribute. Do not: the attribute is what makes the event readable by a consumer that is not on NATS, and the round trip through HTTP or an object store keeps it while the subject is lost at the first hop.

Publish to a subject that names the stream, and let type name the occurrence.

What this does not do

There is no Send(conn, subject, event) helper here, and there will not be one. It would have to import nats.go, which would put NATS in the dependency graph of every service that sends a CloudEvent over HTTP. TestNoTransportIsImported enforces it, in both go/envelope and go/envelope-cloudevents.