Getting started¶
By the end of this you will have sent a CloudEvent between two halves of one program and read every
attribute back at the other end. It needs go/nats for the
transport, and nothing else.
1. Install¶
2. Build an event¶
package main
import (
"time"
"gitlab.com/phpboyscout/go/cloudevents"
)
func order() cloudevents.Event {
return cloudevents.Event{
ID: "01JQ8Z3F7K2M4N6P8R0T2V4W6X",
Source: "/phpbotscout/ingest",
Type: "uk.phpboyscout.orders.created",
Subject: "order-4172",
Time: time.Now().UTC(),
DataContentType: "application/json",
Data: []byte(`{"order":"4172"}`),
}
}
SpecVersion is deliberately absent. There is exactly one value this codec can write, so
Marshal fills it in rather
than making every caller repeat it.
ID and Source together identify the occurrence, and a producer must not reuse the pair. A ULID
or a UUID is the usual choice.
3. Marshal it¶
headers is a map[string][]string holding seven keys — ce-id, ce-source, ce-specversion,
ce-type, ce-subject, ce-time and content-type, with the body carrying the payload
untouched. Values are percent-encoded, so read them back with Unmarshal rather than by hand.
http.Header.Get will not find them
Keys are written in lower case, which is the NATS binding's form. http.Header.Get
canonicalises the key it is given, so hdr.Get("Ce-Id") returns "" against this map. Read it
by iteration or by exact key, and let net/http canonicalise on the wire. Marshal returns an error rather than producing something no consumer would accept, so
a missing Type is caught in your process rather than in somebody else's subscriber.
4. Send it¶
nats.Header is map[string][]string, so the conversion is a conversion and not a copy:
import "github.com/nats-io/nats.go"
msg := &nats.Msg{
Subject: "orders.created",
Header: nats.Header(headers),
Data: body,
}
if err := conn.PublishMsg(msg); err != nil {
return err
}
5. Read it at the other end¶
sub, err := client.Subscribe(ctx, "orders.created", func(msg *nats.Msg) {
e, err := cloudevents.Unmarshal(msg.Header, msg.Data)
if err != nil {
// e still carries whatever could be read — log e.ID, not "a message failed".
logger.Error("refused a CloudEvent", "id", e.ID, "source", e.Source, "err", err)
return
}
logger.Info("received", "type", e.Type, "subject", e.Subject)
})
That error branch is the part worth pausing on. Unmarshal refuses a malformed event, and hands
back the attributes it did manage to read — because core NATS has no dead letter, so a dropped
message is gone unless something recorded which one it was. See
Handle a refused event.
Where next¶
- The wire format — every header, and which are required.
- Send a CloudEvent over HTTP — the same codec, a different
Header. - Why Unmarshal refuses — the strictness, and its cost.