View on GitHub

stdn-api-guidelines

API guidelines STDN

MUST ensure that data change events match the APIs resource [67]

All asynchronous functionality (events, commands and callbacks) must be based on resources like REST APIs. This allows for a convenient and familiar grouping of functionality. If a REST API is already defined for a resource then the same resource name and data model should be used for the asynchronous functions.

Sync https://api.stedin.net/users/v1

Async https://saint-servicebus-prd.servicebus.windows.net/users.v1.event.updated/messages/head

The event payload should contain the full resource (event-carried state transfer) or a subset of the fields (event notification). The structure of the resource should follow the same structure as the API resource.

It is encouraged to send significant details of the resource like the ID and the status as event headers. The consumer can use this to select and prioritize the messages it receives.

MUST use a separate topic if messages contains sensitive data [90]

If one subscriber receives data that other subscribers are not allowed to see then a separate topic must be created. For example when the resource contains customer information name, address and date of birth. And if one or more consumers are not allowed to receive the date of birth data, then a separate topic needs to be created for the smaller data set.

MUST document the asycn functions in the OpenAPI specification [68]

The events and callbacks must be documented using the callback feature of OpenAPI specification. By doing this, all API documentation is available in one place via the portal. If in the future Azure will support the AsyncAPI specification we can reevaluate this approach.

The OpenAPI specification assumes the use of webhooks instead of a message broker when defining callbacks so not all aspects of this feature can be used as intended.

Events

paths:
  /events:
    get:
      summary: Work request events
      operationId: work-request-events
      description: Events emitted for work requests
      responses:
        '200':
          description: OK
      callbacks:
        workrequests-status-updated-event:
          workrequests.v1.event.status-updated:
            post:
              parameters:
                - name: WorkorderId
                  in: header
                  required: true
                  example: 121915374-0
                  schema:
                    type: string
                - name: WorkorderStatus
                  in: header
                  required: true
                  example: Completed
                  schema:
                    type: string
              requestBody:
                description: OK
                content:
                  application/json:
                    schema:
                      $ref: '#/components/schemas/workRequestStatusCallback'
              responses:
                '200':
                  description: OK

Callbacks

The client can specify the callback address with a query parameter or a header. Query parameter expressions: {$request.query.replyToQueue} Header expressions: {$request.header.ReplyToQueue}

A custom claim in the JWT token is also a possibility worth exploring.

paths:
  /{id}:
    put:
      summary: Update a single work request
      operationId: put-work-request
      description: Update a single work request
      parameters:
      - name: ReplyToQueue
        description: Queue used for out-of band callback
        in: header
        example: meterstates.v1.callback.voltagemeasurementvalues.storingen-en-onderhoud
        schema:
          type: string
        required: false
      - name: replyToQueue
        description: Queue used for out-of band callback
        in: query
        example: meterstates.v1.callback.voltagemeasurementvalues.storingen-en-onderhoud
        schema:
          type: string
        required: false
      callbacks:
        work-request-update-callback: # callback name
          {$request.header.ReplyToQueue}:
            post: # always 'post'
              requestBody:
                content:
                  application/json:
                    schema:
                      $ref: '#/components/schemas/workRequest'
              responses:
                '200':
                  description: OK

Naming conventions

MUST use a static queue or topic name [69]

Base structure: <resource>.v<major-version>.<function> Functions: event, callback, command

Queue and topic names are considered static and can’t contain dynamic parts like an invoice identifier. Azure ServiceBus doesn’t support dynamic entities and doesn’t support wildcard subscriptions. Queues and topics can be created via the REST API but this required owner permissions on the entire namespace.

Event topic naming

Structure: <resource>.v<major-version>.event.<event-name>.(<sub-event-name/resource-field>)

Examples: A mailing was rendered: mailings.v1.event.rendered A mailing was sent: mailings.v1.event.sent A mailing was updated: mailings.v1.event.updated An invoice was paid with ideal: invoice.v1.event.paid.ideal An SAP A&V project dossier was cancelled: internal.sapav.projectdossiers.v1.event.cancelled

Callback queue naming

Callback queues must have the receiver’s name as last part. This name should be name of the app registration in lowercase without spaces.

Structure: <resource>.v<major-version>.callback.(<command-name>).<receiver-name>

Examples: For all commands performed for consumer SAP C4C: mailings.v1.callback.sap-c4c For approve commands performed for consumer SAP C4C: salesquotations.v1.callback.approve.sap-c4c

MUST contain a version number [70]

The service version number must be part of the topic and queue names. This supports running multiple versions on an API at the same time. Versions are indicated with a lowercase “v” followed by the major version number. This is short yet recognizable and similar to the version in the URL of REST APIs.

SHOULD use verbs in simple present tense (like “approve”) for commands [71]

Command, event and callback are special words to describe the message function. The term action (for commands) and event are used by the AsyncAPI community. Commands should use verbs in simple present tense (like “approve”)

Examples:

For all commands performed for consumer SAP C4C: mailings.v1.callback.sap-c4c
For approve commands performed for consumer SAP C4C: salesquotations.v1.callback.approve.sap-c4c

SHOULD use verbs in past perfect tense (like “approved”) for events [72]

Events should use verbs in past perfect tense (like “approved”).

Examples:

A mailing was rendered: mailings.v1.event.rendered
A mailing was sent: mailings.v1.event.sent
A mailing was updated: mailings.v1.event.updated
An invoice was paid with ideal: invoice.v1.event.paid.ideal
An SAP A&V project dossier was cancelled: internal.sapav.projectdossiers.v1.event.cancelled

MUST have the receiver’s name as last part for callback queues [73]

Callback queues must have the receiver’s name as last part. This name should be name of the app registration in lowercase without spaces.

Structure: <resource>.v<major-version>.callback.(<command-name>).<receiver-name>

Examples:

For all commands performed for consumer SAP C4C: mailings.v1.callback.sap-c4c
For approve commands performed for consumer SAP C4C: salesquotations.v1.callback.approve.sap-c4c

Azure ServiceBus also supports message sessions which allow multiple consumers to use the same queue or topic to perform request-response. This feature however is not available via the REST API. Because we want to use the same approach for all clients this is not usable.

MUST use client name as topic subscription name [74]

Subscriptions fall hierarchically under a topic in Azure ServiceBus and therefor only have to be unique for that particular topic. This allows for using just the client name in similar convention as the callback queues.

Examples: The subscription for Outsystems KGA: outsystemskga The subscription for SAP A&V: sapav

Performance

SHOULD expire in 14 days [75]

Messages that are not consumed are eventually deleted. By default the expiration is set to 14 days.

MAY use a defer queue [76]

If you as consumer want to be able to postpone the delivery of events. For example when you receive events that can’t be processed until a certain condition is met. You can schedule these messages by sending them back to the queue with the ScheduledEnqueueTimeUtc property. The message will than be delivered again on the specified time.

MAY use filtering [77]

It is possible for a consumer to express its interest in certain events by using a topic filter.

The following filters are currently supported in the API pipeline:

As the SQL filter can have quite a performance impact on the service bus, we strongly request you to use the correlation filter, unless it cannot support the required filtering.

For more information about topic filtering, please take a look at the cookbook: https://dev.azure.com/StedinNetbeheer/Stedin/_wiki/wikis/API%20Cookbook/6714/13.-Consume-an-API?anchor=subscription-filtering

Security

Messages can be send to or received from Azure ServiceBus in two ways: using AMQP or REST.

AMQP stands for Advanced Message Queuing Protocol and is an open standard application layer protocol for messaging. What HTTP is for request/response communication AMQP is for messaging. AMQP is for messaging more efficient than HTTP, because it maintains the connection to Azure ServiceBus. It also implements batching and prefetching.

The HTTP REST API provides an easy alternative for all systems not capable of using AMQP.

REST MUST be secured with Oauth2.0 token [78]

Clients will have to authenticate by proving their credentials and requesting a token from the Token API (v1/oauth/token). The following scope must be used on the token request: https://servicebus.azure.net/.default The access token must be sent by setting a ‘Bearer token’ in the ‘Authorization’ header in the following manner: Authorization: Bearer {access_token}

REST MAY use Azure EventGrid to notify the receiver of new messages [79]

Both HTTP methods require the receiver to poll ServiceBus for new messages. To facilitate a HTTP push, Azure EventGrid can be used which has an integration with ServiceBus. EventGrid will notify the receiver of new messages after which it can retrieve the messages as described above.

So EventGrid will not send the actual message payload but an ActiveMessagesAvailableWithNoListeners or DeadletterMessagesAvailableWithNoListener EventGrid event. When your application receives such event it should continue to read the queue or topic subscription while there are messages available.

EventGrid will send a notification immediately when a new message arrives or every 2 minutes while there are no active receivers on the queue.

When a consumer reads a message it is marked as active for about 5 minutes. When a consumer is marked active is will not receive notifications. So if a message arrives soon after another your application will not receive a new notification until 5 minutes later when the receive is not marked as active anymore. If this is not acceptable we advice you as consumer to implement a frequent (each minute) poll instead of relying on the EventGrid notifications.

image.png

REST MUST secure webhook with API key and Oauth2.0 token [80]

The connection between your Event Subscription and your webhook endpoint must be secured with a apikey or a Oauth2.0 token.

AMQP MUST be secured with SAS tokens on entity level [81]

When using AMQP authentication is done using SAS tokens with permissions on entity level

Events MUST have permissions applied on entity (queue, topic, subscription) level [82]

Event topics and subscriptions

Service providers must have write access to all event topics of its own APIs: <resource>.v<major-version>.event.* Service consumers must have read access to their own topic subscription of an API it has subscribed to: <consumer-name> (as part of a topics) Azure ServiceBus doesn’t support assigning roles on subscription level. Any receiver allowed to read a topic can read all subscriptions of that topic.

Callback queues

Service providers must have write access to all callback queues of its own APIs: <resource>.v<major-version>.callback.* Service consumers must have read access to their own callback queue of an API it has subscribed to: <resource>.v<major-version>.callback.<consumer-name>

Command queues

Service providers must have read access to all command queues of its own APIs: <resource>.v<major-version>.command.* Service consumers must have write access to all command queues of an API it has subscribed to: <resource>.v<major-version>.command.*

  Consumer publisher
Event Topics read write
Event Subscriptions not supported not supported
Command Queues write read
Callback Queues read write