Event stream specification

An event stream is an abstraction that allows multiple messages to be sent asynchronously between a client and server. Event streams support both duplex and simplex streaming. The serialization format and framing of messages sent over event streams is defined by the protocol of a service.

An operation can send an event stream as part of its input or output. An event stream is formed when an input or output member of an operation is marked with the eventStream trait. A member that targets a structure is a single-event event stream, and a member that targets a union is a multi-event event stream.

Single-event event streams

A single-event event stream is an event stream that streams zero or more instances of a specific structure shape.

The following example defines an operation that uses a single-event event stream in its input:

namespace smithy.example

operation PublishMessages {
    input: PublishMessagesInput
}

structure PublishMessagesInput {
    room: String,

    @eventStream
    messages: Message,
}

structure Message {
    message: String,
}
{
    "smithy": "0.5.0",
    "shapes": {
        "smithy.example#PublishMessages": {
            "type": "operation",
            "input": {
                "target": "smithy.example#PublishMessagesInput"
            }
        },
        "smithy.example#PublishMessagesInput": {
            "type": "structure",
            "members": {
                "room": {
                    "target": "smithy.api#String"
                },
                "messages": {
                    "target": "smithy.example#Message",
                    "traits": {
                        "smithy.api#eventStream": true
                    }
                }
            }
        },
        "smithy.example#Message": {
            "type": "structure",
            "members": {
                "message": {
                    "target": "smithy.api#String"
                }
            }
        }
    }
}

The following example defines an operation that uses a single-event event stream in its output:

namespace smithy.example

operation SubscribeToMovements {
    output: SubscribeToMovementsOutput
}

structure SubscribeToMovementsOutput {
    @eventStream
    movements: Movement,
}

structure Movement {
    angle: Float,
    velocity: Float,
}
{
    "smithy": "0.5.0",
    "shapes": {
        "smithy.example#SubscribeToMovements": {
            "type": "operation",
            "output": {
                "target": "smithy.example#SubscribeToMovementsOutput"
            }
        },
        "smithy.example#SubscribeToMovementsOutput": {
            "type": "structure",
            "members": {
                "movements": {
                    "target": "smithy.example#Movement",
                    "traits": {
                        "smithy.api#eventStream": true
                    }
                }
            }
        },
        "smithy.example#Movement": {
            "type": "structure",
            "members": {
                "angle": {
                    "target": "smithy.api#Float"
                },
                "velocity": {
                    "target": "smithy.api#Float"
                }
            }
        }
    }
}

The name of the event sent over a single-event event stream is the name of the member that is targeted by the eventStream trait.

Single-event client behavior

Clients that send or receive single-event event streams are expected to provide an abstraction to end-users that allows values to be produced or consumed asynchronously for the targeted event structure. Because a single-event event stream does not utilize named events like a multi-event event stream, functionality used to dispatch based on named events is unnecessary. Clients MUST provide access to the initial-message of an event stream when necessary.

Multi-event event streams

A multi-event event stream is an event stream that streams any number of named event structure shapes defined by a union. It is formed when the eventStream trait is applied to a member that targets a union. Each member of the targeted union MUST target a structure shape. The member names of the union define the name that is used to identify each event that is sent over the event stream.

The following example defines an operation that uses a multi-event event stream in its input by referencing a member that targets a union:

namespace smithy.example

operation PublishMessages {
    input: PublishMessagesInput
}

structure PublishMessagesInput {
    room: String,

    @eventStream
    messages: PublishEvents,
}

union PublishEvents {
    message: Message,
    leave: LeaveEvent,
}

structure Message {
    message: String,
}

structure LeaveEvent {}
{
    "smithy": "0.5.0",
    "shapes": {
        "smithy.example#PublishMessages": {
            "type": "operation",
            "input": {
                "target": "smithy.example#PublishMessagesInput"
            }
        },
        "smithy.example#PublishMessagesInput": {
            "type": "structure",
            "members": {
                "room": {
                    "target": "smithy.api#String"
                },
                "messages": {
                    "target": "smithy.example#PublishEvents",
                    "traits": {
                        "smithy.api#eventStream": true
                    }
                }
            }
        },
        "smithy.example#PublishEvents": {
            "type": "union",
            "members": {
                "message": {
                    "target": "smithy.example#Message"
                },
                "leave": {
                    "target": "smithy.example#LeaveEvent"
                }
            }
        },
        "smithy.example#Message": {
            "type": "structure",
            "members": {
                "message": {
                    "target": "smithy.api#String"
                }
            }
        }
    }
}

The following example defines an operation that uses a multi-event event stream in its output:

namespace smithy.example

operation SubscribeToMovements {
    output: SubscribeToMovementsOutput
}

structure SubscribeToMovementsOutput {
    @eventStream
    movements: MovementEvents,
}

union MovementEvents {
    up: Movement,
    down: Movement,
    left: Movement,
    right: Movement,
}

structure Movement {
    velocity: Float,
}
{
    "smithy": "0.5.0",
    "shapes": {
        "smithy.example#SubscribeToMovements": {
            "type": "operation",
            "output": {
                "target": "smithy.example#SubscribeToMovementsOutput"
            }
        },
        "smithy.example#SubscribeToMovementsOutput": {
            "type": "structure",
            "members": {
                "movements": {
                    "target": "smithy.example#Message",
                    "traits": {
                        "smithy.api#eventStream": true
                    }
                }
            }
        },
        "smithy.example#MovementEvents": {
            "type": "union",
            "members": {
                "up": {
                    "target": "smithy.example#Movement"
                },
                "down": {
                    "target": "smithy.example#Movement"
                },
                "left": {
                    "target": "smithy.example#Movement"
                },
                "right": {
                    "target": "smithy.example#Movement"
                }
            }
        },
        "smithy.example#Movement": {
            "type": "structure",
            "members": {
                "velocity": {
                    "target": "smithy.api#Float"
                }
            }
        }
    }
}

Multi-event client behavior

Clients that send or receive multi-event event streams are expected to provide an abstraction to end-users that allows values to be produced or consumed asynchronously for each named member of the targeted union. Adding new events to an event stream union is considered a backward compatible change; clients SHOULD NOT fail when an unknown event is received. Clients MUST provide access to the initial-message of an event stream when necessary.

Clients SHOULD expose type-safe functionality that is used to dispatch based on the name of an event. For example, given the following event stream,

namespace smithy.example

operation SubscribeToEvents {
    output: SubscribeToEventsOutput
}

structure SubscribeToEventsOutput {
    @eventStream
    events: Events,
}

union Events {
    a: Event1,
    b: Event2,
    c: Event3,
}

structure Event1 {}
structure Event2 {}
structure Event3 {}

An abstraction SHOULD be provided that is used to dispatch based on the name of an event (that is, a, b, or c) and provide the associated type (for example, when a is received, an event of type Event1 is provided).

Initial messages

An initial message is comprised of the top-level input or output members of an operation that are not targeted by the eventStream trait. Initial messages provide an opportunity for a client or server to provide metadata about an event stream before transmitting events.

Important

Not all protocols support initial messages. Check trait binding and protocol documentation before adding initial messages to an operation.

Initial-request

An initial-request is an initial message that can be sent from a client to a server for an operation with an input event stream. The structure of an initial-request is the input of an operation with no value provided for the event stream member. An initial-request, if sent, is sent from a client to a server before sending any event stream events.

When using HTTP bindings, initial-request fields are mapped to specific locations in the HTTP request such as headers or the URI. In other bindings or protocols, the initial-request can be sent however is necessary for the protocol.

The following example defines an operation with an input event stream with an initial-request. The client will first send the initial-request to the service, followed by the events sent in the payload of the HTTP message.

namespace smithy.example

@http(method: "POST", uri: "/messages/{room}")
operation PublishMessages {
    input: PublishMessagesInput
}

structure PublishMessagesInput {
    @httpLabel
    room: String,

    @httpPayload
    @eventStream
    messages: Message,
}

structure Message {
    message: String,
}
{
    "smithy": "0.5.0",
    "shapes": {
        "smithy.example#PublishMessages": {
            "type": "operation",
            "input": {
                "target": "smithy.example#PublishMessagesInput"
            },
            "traits": {
                "smithy.api#http": {
                    "uri": "/messages/{room}",
                    "method": "POST"
                }
            }
        },
        "smithy.example#PublishMessagesInput": {
            "type": "structure",
            "members": {
                "room": {
                    "target": "smithy.api#String",
                    "traits": {
                        "smithy.api#httpLabel:": true
                    }
                },
                "messages": {
                    "target": "smithy.example#Message",
                    "traits": {
                        "smithy.api#httpPayload": true,
                        "smithy.api#eventStream": true
                    }
                }
            }
        },
        "smithy.example#Message": {
            "type": "structure",
            "members": {
                "message": {
                    "target": "smithy.api#String"
                }
            }
        }
    }
}

Initial-response

An initial-response is an initial message that can be sent from a server to a client for an operation with an output event stream. The structure of an initial-response is the output of an operation with no value provided for the event stream member. An initial-response, if sent, is sent from the server to the client before sending any event stream events.

When using HTTP bindings, initial-response fields are mapped to HTTP headers. In other protocols, the initial-response can be sent however is necessary for the protocol.

The following example defines an operation with an output event stream with an initial-response. The client will first receive and process the initial-response, followed by the events sent in the payload of the HTTP message.

namespace smithy.example

@http(method: "GET", uri: "/messages/{room}")
operation SubscribeToMessages {
    input: SubscribeToMessagesInput,
    output: SubscribeToMessagesOutput
}

structure SubscribeToMessagesInput {
    @httpLabel
    room: String
}

structure SubscribeToMessagesOutput {
    @httpHeader("X-Connection-Lifetime")
    connectionLifetime: Integer,

    @httpPayload
    @eventStream
    messages: Message,
}
{
    "smithy": "0.5.0",
    "shapes": {
        "smithy.example#PublishMessages": {
            "type": "operation",
            "input": {
                "target": "smithy.example#PublishMessagesInput"
            },
            "traits": {
                "smithy.api#http": {
                    "uri": "/messages/{room}",
                    "method": "POST"
                }
            }
        },
        "smithy.example#SubscribeToMessagesInput": {
            "type": "structure",
            "members": {
                "room": {
                    "target": "smithy.api#String",
                    "traits": {
                        "smithy.api#httpLabel:": true
                    }
                }
            }
        },
        "smithy.example#SubscribeToMessagesOutput": {
            "type": "structure",
            "members": {
                "connectionLifetime": {
                    "target": "smithy.api#Integer",
                    "traits": {
                        "smithy.api#httpHeader:": "X-Connection-Lifetime"
                    }
                },
                "messages": {
                    "target": "smithy.example#Message",
                    "traits": {
                        "smithy.api#httpPayload": true,
                        "smithy.api#eventStream": true
                    }
                }
            }
        }
    }
}

Initial message client and server behavior

Initial messages, if received, MUST be provided to applications before event stream events.

It is a backward compatible change to add an initial-request or initial-response to an existing operation; clients MUST NOT fail if an unexpected initial-request or initial-response is received. Clients and servers MUST NOT fail if an initial-request or initial-response is not received for an initial message that contains only optional members.

Event message serialization

While the framing and serialization of an event stream is protocol-specific, traits can be used to influence the serialization of an event stream event. Structure members that are sent as part of an event stream are serialized in either a header or the payload of an event.

The eventHeader trait is used to serialize a structure member as an event header. The payload of an event is defined by either marking a single member with the eventPayload trait, or by combining all members that are not marked with the eventHeader or eventPayload trait into a protocol-specific document.

The following example serializes the "a" and "b" members as event headers and the "c" member as the payload.

structure ExampleEvent {
    @eventHeader
    a: String,

    @eventHeader
    b: String,

    @eventPayload
    c: Blob,
}
{
    "smithy": "0.5.0",
    "shapes": {
        "smithy.example#ExampleEvent": {
            "type": "structure",
            "members": {
                "a": {
                    "target": "smithy.api#String",
                    "traits": {
                        "smithy.api#eventPayload": true
                    }
                },
                "b": {
                    "target": "smithy.api#String",
                    "traits": {
                        "smithy.api#eventPayload": true
                    }
                },
                "c": {
                    "target": "smithy.api#Blob",
                    "traits": {
                        "smithy.api#eventPayload": true
                    }
                }
            }
        }
    }
}

The following example serializes the "a", "b", and "c" members as the payload of the event using a protocol-specific document. For example, when using a JSON based protocol, the event payload is serialized as a JSON object:

structure ExampleEvent {
    a: String,
    b: String,
    c: Blob,
}
{
    "smithy": "0.5.0",
    "shapes": {
        "smithy.example#ExampleEvent": {
            "type": "structure",
            "members": {
                "a": {
                    "target": "smithy.api#String"
                },
                "b": {
                    "target": "smithy.api#String"
                },
                "c": {
                    "target": "smithy.api#Blob"
                }
            }
        }
    }
}

Event stream traits

eventStream trait

Summary
Configures a member of an operation input or output as an event stream.
Trait selector

operation -[input, output]-> structure > :test(member > :each(structure, union))

An operation input or output member that targets a structure or union.

Value type
Annotation trait.
Conflicts with
required trait
Examples

A structure that contains a member marked with the eventStream trait can only be referenced by operation input or output shapes. Structures that contain an event stream cannot be referenced by members or used as part of an error.

The member targeted by the eventStream trait MUST NOT be marked as required because the input or output structure also functions as an initial-message.

eventHeader trait

Summary
Binds a member of a structure to be serialized as an event header when sent through an event stream.
Trait selector
member:of(structure):test( > :each(boolean, byte, short, integer, long, blob, string, timestamp))

Member of a structure that targets a boolean, byte, short, integer, long, blob, string, or timestamp shape

Value type
Annotation trait.
Conflicts with
eventPayload trait

Important

Not all protocols support event headers. For example, MQTT version 3.1.1 does not support custom message headers. It is a protocol-level concern as to if and how event stream headers are serialized.

The following example defines multiple event headers:

structure ExampleEvent {
    @eventHeader
    a: String,

    @eventHeader
    b: String,
}
{
    "smithy": "0.5.0",
    "shapes": {
        "smithy.example#ExampleEvent": {
            "type": "structure",
            "members": {
                "a": {
                    "target": "smithy.api#String",
                    "traits": {
                        "smithy.api#eventHeader": true
                    }
                },
                "b": {
                    "target": "smithy.api#String",
                    "traits": {
                        "smithy.api#eventHeader": true
                    }
                }
            }
        }
    }
}

eventPayload trait

Summary
Binds a member of a structure to be serialized as the payload of an event sent through an event stream.
Trait selector
member:of(structure):test(> :each(blob, string, structure, union))

Structure member that targets a blob, string, structure, or union

Value type
Annotation trait.
Conflicts with
eventHeader trait
Validation
  1. This trait is structurally exclusive, meaning only a single member of a structure can be targeted by the trait.
  2. If the eventPayload trait is applied to a structure member, then all other members of the structure MUST be marked with the eventHeader trait.

Event payload is serialized using the following logic:

  • A blob and string is serialized using the bytes of the string or blob.
  • A structure and union is serialized as a protocol-specific document.

The following example defines an event header and sends a blob as the payload of an event:

structure ExampleEvent {
    @eventPayload
    a: String,

    @eventHeader
    b: String,
}
{
    "smithy": "0.5.0",
    "shapes": {
        "smithy.example#ExampleEvent": {
            "type": "structure",
            "members": {
                "a": {
                    "target": "smithy.api#String",
                    "traits": {
                        "smithy.api#eventPayload": true
                    }
                },
                "b": {
                    "target": "smithy.api#String",
                    "traits": {
                        "smithy.api#eventHeader": true
                    }
                }
            }
        }
    }
}

The following structure is invalid because the "a" member is bound to the eventPayload, and the "b" member is not bound to an eventHeader.

structure ExampleEvent {
    @eventPayload
    a: String,

    b: String,
    // ^ Error: not bound to an eventHeader.
}
HTTP protocol bindings →