docs: Document expectations for protocol bindings

Signed-off-by: Jon Skeet <jonskeet@google.com>
This commit is contained in:
Jon Skeet 2021-03-12 13:10:49 +00:00 committed by Jon Skeet
parent 7f6fa9380b
commit 83cdc5a1b4
2 changed files with 183 additions and 0 deletions

6
docs/README.md Normal file
View File

@ -0,0 +1,6 @@
# SDK documentation
This directory contains documentation on:
- Using the SDK as a consumer
- Implementing new event formats and protocol bindings

177
docs/bindings.md Normal file
View File

@ -0,0 +1,177 @@
# Implementing a protocol binding
From the [specification](https://github.com/cloudevents/spec/blob/v1.0.1/spec.md#protocol-binding):
> 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.
## Conversions to CloudEvent
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.
### IsCloudEvent
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)
- The presence of a CloudEvents specification version header (for
binary mode events)
### ToCloudEvent methods
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` and `formatter` are non-null. (The
`extensionAttributes` 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 the `formatter` parameter,
using its `DecodeStructuredModeMessage` 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 the
`CloudEvent.Data` property appropriately.
- Return the result of `CloudEvent.ValidateForConversion` which
will validate the event, and either return the original reference if
the event is valid, or throw an appropriate `ArgumentException`
otherwise.
## Conversions from CloudEvent to protocol message types
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:
```csharp
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:
```csharp
// 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` and `formatter` should be non-null
- In a `CopyTo...` method, `destination` should be non-null
- The `contentMode` should be a known, supported value
- Call `cloudEvent.ValidateForConversion(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.
- For binary mode encoding:
- Call `formatter.EncodeBinaryModeEventData` to encode
the data within the CloudEvent
- Populate metadata in the message from the attributes in the
CloudEvent.
- 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.