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
paramsoverload) - A single extension attribute, or multiple known individual ones
(call uses the
paramsoverload) - 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
messageandformatterare non-null. (TheextensionAttributesparameter 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
CloudEventFormatterspecified in theformatterparameter, using itsDecodeStructuredModeMessagemethod. 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.FromVersionIdand check for a null return value. - Construct a
CloudEventinstance with the given specification version and extension attributes. - Look for all CloudEvent attributes within metadata, and populate
the attributes within the
CloudEventinstance. - If the message contains content, call the
formatter.DecodeBinaryModeEventDatamethod to populate theCloudEvent.Dataproperty appropriately. - Return the result of
Validation.CheckCloudEventArgumentwhich will validate the event, and either return the original reference if the event is valid, or throw an appropriateArgumentExceptionotherwise.
- 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):
cloudEventandformattershould be non-null (CheckCloudEventArgumentwill validate this for thecloudEventparameter)- In a
CopyTo...method,destinationshould be non-null - The
contentModeshould be a known, supported value - Call
Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent))for validation of the original CloudEvent.
- For structured mode encoding:
- Call
formatter.EncodeStructuredModeMessageto encode the CloudEvent and indicate the resulting content type for the protocol message.
- Call
- For binary mode encoding:
- Call
formatter.EncodeBinaryModeEventDatato encode the data within the CloudEvent - Call
formatter.GetOrInferDataContentTypeto 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.