CloudEvents
Bielu.AspNetCore.AsyncApi.CloudEvents describes a document's messages as CloudEvents 1.0
events. CloudEvents is the CNCF's common envelope for event data: every event carries an id, a source, a
specversion and a type, in protocol headers (binary mode) or around the data (structured mode). Without this
package the AsyncAPI document describes only the data, and consumers have to learn about the envelope somewhere else.
dotnet add package Bielu.AspNetCore.AsyncApi.CloudEvents
builder.Services.AddAsyncApi(options => options
.AddServer("kafka", "broker:9092", "kafka")
.AddCloudEvents(cloudEvents => cloudEvents
.WithSource("/orders")
.WithTypePrefix("com.example.orders.")));
AddCloudEvents() adds a document transformer. Document transformers run in the order they are added, so add it
after anything else that adds messages.
Binary mode
Binary mode is the default. The data stays the payload, and each message's headers schema gains the context attributes:
| Attribute | Schema | Required |
|---|---|---|
id |
non-empty string | yes |
source |
URI reference, pinned with const when a source is configured |
yes |
specversion |
const: "1.0" |
yes |
type |
const of the event type |
yes |
dataschema |
URI, pinned when configured for the message | no |
subject |
string | no |
time |
date-time |
no |
datacontenttype is left out. In binary mode every binding carries it as the protocol's own content type, which is the
message's contentType.
Header names depend on the CloudEvents protocol binding:
| Binding | Server protocols | id header |
|---|---|---|
| HTTP | http, https, ws, wss, anything else |
ce-id |
| Kafka | kafka, kafka-secure |
ce_id |
| AMQP | amqp, amqps |
cloudEvents:id |
| MQTT 5 | mqtt, mqtts, secure-mqtt, mqtt5 |
id (user property) |
With ProtocolBinding = Auto, the default, the binding is chosen per message from the protocols of the servers its
channels use. A channel without servers is on every server. When those protocols disagree, or there are no servers,
the binding falls back to HTTP. To choose one yourself, call UseProtocolBinding(CloudEventsProtocolBinding.Kafka).
A message that already has headers, for example from [Message(HeadersType = typeof(...))], keeps them: the result is
allOf of the existing schema and the CloudEvents attributes.
Structured mode
options.AddCloudEvents(cloudEvents => cloudEvents.UseMode(CloudEventsMode.Structured));
The payload becomes the envelope, and the message's contentType becomes application/cloudevents+json:
{
"type": "object",
"required": ["id", "source", "specversion", "type"],
"properties": {
"id": { "type": "string", "minLength": 1 },
"source": { "type": "string", "format": "uri-reference", "const": "/orders" },
"specversion": { "type": "string", "const": "1.0" },
"type": { "type": "string", "const": "com.example.orders.orderPlaced" },
"datacontenttype": { "type": "string", "const": "application/json" },
"data": { "$ref": "#/components/schemas/OrderPlaced" }
}
}
datacontenttype is pinned to the message's original content type, or to the document's defaultContentType, or to
application/json. A payload in another schema format, such as Avro, cannot be the data of a JSON Schema envelope,
so such a message is left unchanged.
Event type and source
| Setting | Effect |
|---|---|
WithTypePrefix("com.example.") |
type is the prefix plus the message name (com.example.orderPlaced). |
WithSource("/orders") |
source is pinned to /orders on every message. |
ConfigureMessage(key, m => …) |
Overrides Type, Source, DataSchema or Mode for one message, by its key under components/messages. |
MessageFilter = key => … |
Keeps only the messages it returns true for. |
Wolverine
For messages that the Wolverine integration documents, call DescribeCloudEvents() on
AddWolverine(...) instead of AddCloudEvents(). It applies the structured envelope only on the endpoints that
actually send CloudEvents (SignalR and InteropWithCloudEvents() endpoints), with the type, source and
datacontenttype that Wolverine writes. Elsewhere Wolverine sends its own headers, not CloudEvents attributes.
AddCloudEvents() skips any message that is already a structured CloudEvent, so the two can be used in the same
document.
Examples
Message examples gain the attributes too: header values in binary mode, and an envelope around the example payload in
structured mode. They use the configured source (or /) and a placeholder id.
Conformance
The test suite validates the output of both modes against the official AsyncAPI 2.6.0 and 3.1.0 JSON Schemas.