From the specification:
A protocol binding describes how events are sent and received over a given protocol.
In SDK terms, this usually means methods converting between
the SDK CloudEvent
type and protocol-specific request/response
types.
This document describes suggested conventions for implementing a new protocol binding. These are only conventions rather than requirements, but following them will promote consistency, leading to a more predictable user experience.
In this document, it is assumed that the binding has the concepts of "structured" or "binary" content modes. Readers should make appropriate choices where this is not the case.
It is encouraged to provide conversions in both directions where this makes sense, but that won't be the case in all situations.
Provide extension methods on the protocol-specific message types,
with the following pattern, demonstrated with an imaginary
ProtocolMessage
type (such as HttpRequestMessage
or
HttpResponseMessage
):
public static bool IsCloudEvent(this ProtocolMessage message);
public static CloudEvent ToCloudEvent(
this ProtocolMessage message,
CloudEventFormatter formatter,
params CloudEventAtribute[] extensionAttributes);
public static CloudEvent ToCloudEvent(
this ProtocolMessage message,
CloudEventFormatter formatter,
IEnumerable<CloudEventAtribute> extensionAttributes);
Where message content can only be read asynchronously, async methods
(with a type Task<CloudEvent>
or ValueTask<CloudEvent>
and an
Async
method name suffix) should be provided instead.
The reason for providing two overloads is for caller convenience to cover three scenarios:
- No extension attribute (call uses the
params
overload) - A single extension attribute, or multiple known individual ones
(call uses the
params
overload) - An immutable collection of extension attributes, either provided by
the SDK (e.g.
Sampling.AllAttriutes
) or constructed once by the caller.
The IsCloudEvent
method should provide a reasonable indication of
whether or not the message contains a CloudEvent, but should not
perform full decoding and validation. Typically it checks metadata
for one of:
- A content type beginning with "application/cloudevents" (for structured content mode events) but not beginning with "application/cloudevents-batch" (even if the binding does not currently support batch events)
- The presence of a CloudEvents specification version header (for binary mode events)
Typically multiple overloads of ToCloudEvent
will resolve to a
single internal implementation. The implementation should use the
following pseudo-code as structure:
- Validate that
message
andformatter
are non-null. (TheextensionAttributes
parameter may be null, which is equivalent to an empty collection.) - Determine the content mode of the message
- For a structured content mode message, delegate to the
CloudEventFormatter
specified in theformatter
parameter, using itsDecodeStructuredModeMessage
method. The formatter should take care of validating the resulting event. - For binary content mode message:
- Determine the specification version based on metadata. (The
transport may specify a default specification, but typically
this is required metadata which serves to check that the message
is intended to represent a CloudEvent.) Use
CloudEventsSpecVersion.FromVersionId
and check for a null return value. - Construct a
CloudEvent
instance with the given specification version and extension attributes. - Look for all CloudEvent attributes within metadata, and populate
the attributes within the
CloudEvent
instance. - If the message contains content, call the
formatter.DecodeBinaryModeEventData
method to populate theCloudEvent.Data
property appropriately. - Return the result of
Validation.CheckCloudEventArgument
which will validate the event, and either return the original reference if the event is valid, or throw an appropriateArgumentException
otherwise.
- Determine the specification version based on metadata. (The
transport may specify a default specification, but typically
this is required metadata which serves to check that the message
is intended to represent a CloudEvent.) Use
There are two patterns here, depending on whether it's appropriate for the conversion to construct a new instance or whether it should populate an existing instance:
public static ProtocolMessage ToProtocolMessage(
this CloudEvent cloudEvent,
ContentMode contentMode,
CloudEventFormatter formatter);
public static void CopyToProtocolMessage(
this CloudEvent cloudEvent,
ProtocolMessage destination,
ContentMode contentMode,
CloudEventFormatter formatter);
Here the ProtocolMessage
part of the message name varies by target
type. Where the type involved is sufficiently clear, that can be
used directly. If the type involved only has a generic name such as
Message
or Request
, it is useful to qualify it with the protocol
name. Examples:
// HttpContent is already unambiguous.
public static HttpContent ToHttpContent(
this CloudEvent cloudEvent,
ContentMode contentMode,
CloudEventFormatter formatter);
// Message is ambiguous, so clarify it in the method name.
public static Message ToAmqpMessage(
this CloudEvent cloudEvent,
ContentMode contentMode,
CloudEventFormatter formatter);
Again, where asynchrony is required, make the methods async with appropriate changes to the return type and method name.
Even if only a single content mode is currently supported, the
inclusion of the contentMode
parameter provides consistency and a
seamless path for change later. If the protocol inherently means
that contentMode
is meaningless (so can never be supported), it
can be omitted.
Any additional parameters required for the conversion should be
added after the formatter
parameter.
The conversion should follow the following steps of pseudo-code:
- Parameter validation (which may be completed in any order):
cloudEvent
andformatter
should be non-null (CheckCloudEventArgument
will validate this for thecloudEvent
parameter)- In a
CopyTo...
method,destination
should be non-null - The
contentMode
should be a known, supported value - Call
Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent))
for validation of the original CloudEvent.
- For structured mode encoding:
- Call
formatter.EncodeStructuredModeMessage
to encode the CloudEvent and indicate the resulting content type for the protocol message.
- Call
- For binary mode encoding:
- Call
formatter.EncodeBinaryModeEventData
to encode the data within the CloudEvent - Call
formatter.GetOrInferDataContentType
to obtain the appropriate content type, which may be inferred from the data if the CloudEvent itself does not specify the data content type. - Populate metadata in the message from the attributes in the CloudEvent and the content type.
- Call
- For
To...
methods, return the resulting protocol message. This must not be null. (CopyTo...
messages do not return anything.)
Note that depending on the protocol binding, when encoding a CloudEvent in binary mode, it may still be worth populating metadata in the message from the CloudEvent attributes.
Protocol bindings that support batch conversions should introduce equivalent batch methods:
public static bool IsCloudEventBatch(this ProtocolMessage message);
public static IReadOnlyList<CloudEvent> ToCloudEventBatch(
this ProtocolMessage message,
CloudEventFormatter formatter,
params CloudEventAtribute[] extensionAttributes);
public static IReadOnlyList<CloudEvent> ToCloudEventBatch(
this ProtocolMessage message,
CloudEventFormatter formatter,
IEnumerable<CloudEventAtribute> extensionAttributes);
public static ProtocolMessage ToProtocolMessage(
this IReadOnlyList<CloudEvent> cloudEvents,
CloudEventFormatter formatter);
public static void CopyToProtocolMessage(
this IReadOnlyList<CloudEvent> cloudEvents,
ProtocolMessage destination,
CloudEventFormatter formatter);
Note that no ContentMode
is specified when converting to protocol
messages, as there is no ambiguity.