The API surface of your client library must have the most thought as it is the primary interaction that the consumer has with your service.
Platform Support
✅ DO support all LTS versions of Node and newer versions up to and including the latest release.
✅ DO support the following browsers and versions:
- Apple Safari: latest two versions
- Google Chrome: latest two versions
- Microsoft Edge: all supported versions
- Mozilla FireFox: latest two versions
Use caniuse.com to determine whether you can use a given platform feature in the runtime versions you support. Syntax support is provided by TypeScript.
⚠️ YOU SHOULD NOT support IE11. If you have a business justification for IE11 support, contact the Architecture Board.
✅ DO compile without errors on all versions of TypeScript greater than 3.1.
While consumers are fast at adopting new versions of TypeScript, version 3.1 is used by Angular 7, which is still commonly used. Supporting older versions of TypeScript can be a challenge. There are two general approaches:
- Don’t use new features.
- Use
typesVersions
, which might require manual effort to produce typings compatible with older versions based on the new typings.
✅ DO get approval from the Architecture Board to drop support for any platform (except IE11 and Node 6) even if support isn’t required.
Namespaces, NPM Scopes, and Distribution Tags
✅ DO publish your library to the @azure
npm scope.
✅ DO pick a package name that allows the consumer to tie the namespace to the service being used. As a default, use the compressed service name at the end of the namespace. The namespace does NOT change when the branding of the product changes. Avoid the use of marketing names that may change.
✅ DO tag beta packages with the npm distribution tag next
. If there is no generally available release of this package, it should also be tagged latest
.
✅ DO tag generally available npm packages latest
. Generally available packages may also be tagged next
if they include the changes from the most recent beta.
✅ DO use one of the appropriate namespaces (see below) for browser globals when producing UMD builds.
In cases where namespaces are supported, the namespace should be named Azure.<group>.<service>
. All consumer-facing APIs that are commonly used should exist within this namespace. Here:
<group>
is the group for the service (see the list above)<service>
is the service name represented as a single word
✅ DO start the namespace with Azure
.
✅ DO use camel-casing for elements of the namespace.
A compressed service name is the service name without spaces. It may further be shortened if the shortened version is well known in the community. For example, “Azure Media Analytics” would have a compressed service name of “MediaAnalytics”, and “Azure Service Bus” would become “ServiceBus”.
✅ DO use the following list as the group of services (if the target language supports namespaces):
Namespace Group | Functional Area |
---|---|
ai |
Artificial intelligence, including machine learning |
analytics |
Gathering data for metrics or usage |
data |
Dealing with structured data stores like databases |
diagnostics |
Gathering data for diagnosing issues |
digitaltwins |
Digital Twins, digital representations of physical spaces and IoT devices |
identity |
Authentication and authorization |
iot |
Internet of things |
management |
Control Plane (Azure Resource Manager) |
media |
Audio, video, or mixed reality |
messaging |
Messaging services, like push notifications or pub-sub |
search |
Search technologies |
security |
Security and cryptography |
storage |
Storage of unstructured data |
✅ DO place the management (Azure Resource Manager) API in the “management” group. Use the grouping Azure.Management.<group>.<servicename>
for the namespace. Since more services require control plane APIs than data plane APIs, other namespaces may be used explicitly for control plane only. Data plane usage is by exception only. Additional namespaces that can be used for control plane libraries include:
Namespace Group | Functional Area |
---|---|
appmodel |
Application models, such as Functions or App Frameworks |
compute |
Virtual machines, containers, and other compute services |
integration |
Integration services (such as Logic Apps) |
management |
Management services (such as Cost Analytics) |
networking |
Services such as VPN, WAN, and Networking |
Many management
APIs don’t have a data plane. It’s reasonable to place the management library in the Azure.Management
namespace. For example, use Azure.Management.CostAnalysis
, not Azure.Management.Management.CostAnalysis
.
⛔️ DO NOT choose similar names for clients that do different things.
✅ DO register the chosen namespace with the Architecture Board. Open an issue to request the namespace. See the registered namespace list for a list of the currently registered namespaces.
These namespace examples meet the guidelines:
Azure.Data.Cosmos
Azure.Identity.ActiveDirectory
Azure.IoT.DeviceProvisioning
Azure.Storage.Blob
Azure.Messaging.NotificationHubs
(the client library for Notification Hubs)Azure.Management.Messaging.NotificationHubs
(the management client for Notification Hubs)
These examples that don’t meet the guidelines:
Microsoft.Azure.CosmosDB
(not in the Azure namespace, no grouping)Azure.MixedReality.Kinect
(invalid group)Azure.IoT.IoTHub.DeviceProvisioning
(too many levels)
Contact the Architecture Board for advice if the appropriate group isn’t obvious. If you feel your service requires a new group, open a “Design Guidelines Change” request.
The Client API
Your API surface will consist of one or more service clients that the consumer will instantiate to connect to your service, plus a set of supporting types. The basic shape of JavaScript service clients is shown in the following example:
export class ServiceClient {
// client constructors have overloads for handling different
// authentication schemes.
constructor(connectionString: string, options?: ServiceClientOptions);
constructor(host: string, credential: TokenCredential, options?: ServiceClientOptions);
constructor(...) { }
// Service methods. Options take at least an abortSignal.
async createItem(options?: CreateItemOptions): CreateItemResponse;
async deleteItem(options?: DeleteItemOptions): DeleteItemResponse;
// Simple paginated API
listItems(): PagedAsyncIterableIterator<Item, ItemPage> { }
// Clients for sub-resources
getItemClient(itemName: string) { }
}
Client constructors and factories
✅ DO place service client types that the consumer is most likely to interact as a top-level export from your library. That is, the service client type should be something that can be imported directly by the consumer.
✅ DO allow the consumer to construct a service client with the minimal information needed to connect and authenticate to the service.
✅ DO standardize verb prefixes within a set of client libraries for a service (see approved verbs).
The service speaks about specific operations in a cross-language manner within outbound materials (such as documentation, blogs, and public speaking). The service can’t be consistent across languages if the same operation is referred to by different verbs in different languages.
✅ DO support 100% of the features provided by the Azure service the client library represents.
Gaps in functionality cause confusion and frustration among developers. A feature may be omitted if there isn’t support on the platform. For example, a library that depends on local file system access may not work in a browser.
☑️ YOU SHOULD provide overloaded constructors for all client construction scenarios.
Don’t use static methods to construct a client unless an overload would be ambiguous. Prefix the static method with from
if you require a static constructor.
☑️ YOU SHOULD prefer overloads over unions when either:
- Users want to see documentation (for example, signature help) tailored specifically to the parameters they’re passing in, or
- Multiple parameters are correlated.
Unions may be used if neither of these conditions are met. Let’s say we have an API that takes either two numbers or two strings but not both. In this case, the parameters are correlated. If we implemented the types using unions like the following code:
function foo(a: string | number, b: string | number): void {}
We have mistakenly allowed invalid arguments; that is foo(number, string)
and foo(string, number)
. Overloads naturally express this correlation:
function foo(a: string, b: string): void;
function foo(a: number, b: number): void;
function foo(a: string | number, b: string | number): void {}
The overload approach also lets us attach documentation to each overload individually.
// bad example
class ExampleClient {
constructor (connectionString: string, options: ExampleClientOptions);
constructor (url: string, options: ExampleClientOptions);
constructor (urlOrCS: string, options: ExampleClientOptions) {
// have to dig into the first parameter to see whether its
// a url or a connection string. Not ideal.
}
}
// better example
class ExampleClient {
constructor (url: string, options: ExampleClientOptions) {
}
static fromConnectionString(connectionString: string, options: ExampleClientOptions) {
}
}
Options
The guidelines in this section apply to options passed in options bags to clients, whether methods or constructors. When referring to option names, this means the key of the object users must use to specify that option when passing it into a method or constructor.
✅ DO name the type of the options bag as <class name>Options
and <method name>Options
for constructors and methods respectively.
✅ DO name abort signal options abortSignal
.
✅ DO suffix durations with In<Unit>
. Unit should be ms
for milliseconds, and otherwise the name of the unit. Examples include timeoutInMs
and delayInSeconds
.
Retry-specific Options
Many services have a notion of retries and have various means to configure them.
✅ DO use the option names specified in the table below
Option | Values | Usage | Other Names (informational) |
---|---|---|---|
retryMode | ‘fixed’, ‘linear’, ‘exponential’ | Used to specify the retry strategy | |
maxRetries | number >= 0 | Number of times to retry. 0 effectively disables retrying. | |
retryDelayInMs | number > 0 | Delay between retries. For linear and exponential strategies, this is the initial retry delay and increases thereafter based on the strategy used. | |
maxRetryDelayInMs | number > 0 | Maximum delay between retries. For linear and exponential strategies, this effectively clamps the maximum amount of time between retries. | |
tryTimeoutInMs | number > 0 | How long to wait for a particular retry to complete before giving up |
✅ DO support the following retry strategies:
fixed
: retry after some duration, where the duration never changes.exponential
: retry after some duration, where the duration increases exponentially after each attempt.
Response formats
Requests to the service fall into two basic groups - methods that make a single logical request, or a deterministic sequence of requests. An example of a single logical request is a request that may be retried inside the operation. An example of a deterministic sequence of requests is a paged operation. The logical entity is a protocol neutral representation of a response. For HTTP, the logical entity may combine data from headers, the body, and the status line. For example, you may add the ETag
header as a property on the logical entity to the deserialized content from the body of the response.
✅ DO optimize for returning the logical entity for a given request. The logical entity MUST represent the information needed in the 99%+ case.
✅ DO make it possible for a developer to get access to the complete response, including the status line, headers, and body.
✅ DO document how to access the raw and streamed response for a request (if exposed by the client library). Include comprehensive samples. We don’t expect all methods to expose a streamed response.
For methods that combine multiple requests into a single call:
⛔️ DO NOT return headers and other per-request metadata unless it’s obvious as to which specific HTTP request the methods return value corresponds to.
✅ DO provide enough information in failure cases for an application to take appropriate corrective action.
⚠️ YOU SHOULD NOT use the following property names within a logical entity:
object
value
Using names that are commonly used as reserved words can cause confusion and will cause consistency issues between languages.
// Service operation method on a service client
public async getProperties(
options: ContainerGetPropertiesOptions = {}
): Promise<Models.ContainerGetPropertiesResponse> {
// ...
}
// Response type, in this case for a service which returns the
// relevant info in headers. Note how the headers are represented
// in first-class properties with intellisense etc.
export type ContainerGetPropertiesResponse = ContainerGetPropertiesHeaders & {
/**
* The underlying HTTP response.
*/
_response: msRest.HttpResponse & {
/**
* The parsed HTTP response headers.
*/
parsedHeaders: ContainerGetPropertiesHeaders;
};
};
export interface ContainerGetPropertiesHeaders {
// ...
/**
* @member {PublicAccessType} [blobPublicAccess] Indicated whether data in
* the container may be accessed publicly and the level of access. Possible
* values include: 'container', 'blob'
*/
blobPublicAccess?: PublicAccessType;
/**
* @member {boolean} [hasImmutabilityPolicy] Indicates whether the container
* has an immutability policy set on it.
*/
hasImmutabilityPolicy?: boolean;
}
Client naming conventions
✅ DO name service client types with the Client suffix.
☑️ YOU SHOULD use one of the approved verbs in the below table when referring to service operations.
Verb | Parameters | Returns | Comments |
---|---|---|---|
create\<Noun> |
key, item | Created item | Create new item. Fails if item already exists. |
upsert\<Noun> |
key, item | Updated or created item | Create new item, or update existing item. Verb is primarily used in database-like services |
set\<Noun> |
key, item | Updated or created item | Create new item, or update existing item. Verb is primarily used for dictionary-like properties of a service |
update\<Noun> |
key, partial item | Updated item | Fails if item doesn’t exist. |
replace\<Noun> |
key, item | Replace existing item | Completely replaces an existing item. Fails if the item doesn’t exist. |
append\<Noun> |
item | Appended item | Add item to a collection. Item will be added last. |
add\<Noun> |
index, item | Added item | Add item to a collection. Item will be added at the given index. |
get\<Noun> |
key | Item | Will return null if item doesn’t exist |
list\<Noun>s |
PagedAsyncIterableIterator<TItem, TPage> |
Return list of items. Returns empty list if no items exist | |
\<noun>Exists |
key | bool |
Return true if the item exists. |
delete\<Noun> |
key | None | Delete an existing item. Will succeed even if item didn’t exist. |
remove\<Noun> |
key | None or removed item | Remove item from a collection. |
⛔️ DO NOT include the Noun
when the operation is operating on the resource itself, For example, if you have an ItemClient
with a delete method, it should be called delete
rather than deleteItem
. The noun is implicitly this
.
✅ DO prefix methods that create or vend subclients with get
and suffix with client
. For example, container.getBlobClient()
.
✅ DO suffix options bag parameters names with Options
, and prefix with the name of the operation. For example, if an operation is called createItem, its options type must be called CreateItemOptions
.
The following are good examples of names for operations in a TypeScript client library:
containerClient.listBlobs();
containerClient.delete();
The following are bad examples:
containerClient.deleteContainer(); // don't include noun for direct manipulation
containerClient.newBlob(); // use create instead of new
containerClient.createOrUpdate(); // use upsert
containerClient.createBlobClient(); // should be `getBlobClient`.
Network requests
When an application makes a network request, the network infrastructure (like routers) and the called service may take a long time to respond. In fact, the network infrastructure may never respond. A well-written application should NEVER give up its control to the network infrastructure or service.
Consider the following examples. An orchestrator needs to stop a service because of a scaling operation, reconfiguration, or upgrading to a new version). The orchestrator typically notifies a running service instance by sending an interrupt signal. The service should stop as quickly as possible when it receives this signal. Similarly, when a web server receives a request, it may set a time limit indicating how much time it’s allowing before giving a response to the user. A UI application may offer the user a cancel button when making a network request.
The best way for consumers to work with cancellation is to think of cancellation objects as forming a tree. For example:
- Cancelling a parent automatically cancels its children.
- Children can time out sooner than their parent but can’t extend the total time.
- Cancellation can happen because of a timeout or an explicit request.
✅ DO accept an AbortSignalLike
parameter on all asynchronous calls. This type is provided by @azure/abort-controller
.
☑️ YOU SHOULD only check cancellation tokens on I/O calls (such as network requests and file loads). Don’t check the cancellation token between I/O calls within the client library (for example, when processing data between I/O calls).
⛔️ DO NOT leak the underlying protocol transport implementation details to the consumer. All types from the protocol transport implementation must be appropriately abstracted.
Authentication
Azure services use different kinds of authentication schemes to allow clients to access the service. Conceptually, there are two entities responsible in this process: a credential and an authentication policy. Credentials provide confidential authentication data. Authentication policies use the data provided by a credential to authenticate requests to the service.
✅ DO support all authentication techniques that the service supports.
✅ DO use credential and authentication policy implementations from the Azure Core library where available.
✅ DO provide credential types that can be used to fetch all data needed to authenticate a request to the service. Credential types should be non-blocking and atomic. Use credential types from the @azure/core-auth
library where possible.
✅ DO provide service client constructors or factories that accept any supported authentication credentials.
Client libraries may support connection strings ONLY IF the service provides a connection string to users via the portal or other tooling. Connection strings are easily integrated into an application by copy/paste from the portal. However, connection strings don’t allow the credentials to be rotated within a running process.
⛔️ DO NOT support constructing a service client with a connection string unless such connection string is available within tooling (for copy/paste operations).
Modern & Idiomatic JavaScript
✅ DO use built-in promises for asynchronous operations. You may provide overloads that take a callback. Don’t import a polyfill or library to implement promises.
Promises were added to JavaScript ES2015. ES2016 and later added async
functions to make working with promises easier. Promises are broadly supported in JavaScript runtimes, including all currently supported versions of Node.
☑️ YOU SHOULD use async
functions for implementing asynchronous library APIs.
If you need to support ES5 and are concerned with library size, use async
when combining asynchronous code with control flow constructs. Use promises for simpler code flows. async
adds code bloat (especially when targeting ES5) when transpiled.
✅ DO use Iterators and Async Iterators for sequences and streams of all sorts.
Both iterators and async iterators are built into JavaScript and easy to consume. Other streaming interfaces (such as node streams) may be used where appropriate as long as they’re idiomatic.
☑️ YOU SHOULD prefer interface types to class types. JavaScript is fundamentally a duck-typed language, and so alternative classes that implement the same interface should be allowed. Declare parameters as interface types over class types whenever possible. Overloads with specific class types are fine but there should be an overload present with the generic interface.
// bad synchronous example
function listItems() {
return {
nextItem() { /*...*/ }
}
}
// better synchronous example
function* listItems() {
/* ... */
}
// bad asynchronous examples
function listItems() {
return Rx.Observable.of(/* ... */)
}
function listItems(callback) {
// fetch items
for (const item of items) {
callback (item)
}
}
// better asynchronous example
async function* listItems() {
for (const item of items) {
yield item;
}
}
~
Modern & Idiomatic TypeScript
✅ DO implement your library in TypeScript.
✅ DO include type declarations for your library.
TypeScript static types provide significant benefit for both the library authors and consumers. TypeScript also compiles modern JavaScript language features for use with older runtimes.
tsconfig.json
Your tsconfig.json
should look similar to the following example:
{
"compilerOptions": {
"declaration": true,
"module": "es6",
"moduleResolution": "node",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"outDir": "./dist-esm",
"target": "es6",
"sourceMap": true,
"declarationMap": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"importHelpers": true
},
"include": ["./src/**/*"],
"exclude": ["node_modules"]
}
✅ DO have at least “node_modules” in the exclude
array. TypeScript shouldn’t needlessly type check your dependencies.
⛔️ DO NOT use the compilerOptions.lib
field. Built in typescript libraries (for example, esnext.asynciterable
) should be included via reference directives. See also Microsoft/TypeScript#27416.
✅ DO set compilerOptions.strict
to true. The strict
flag is a best practice for developers as it provides the best TypeScript experience. The strict
flag also ensures that your type definitions are maximally pedantic.
✅ DO set compilerOptions.esModuleInterop
to true.
✅ DO set compilerOptions.allowSyntheticDefaultImports
to true.
✅ DO set compilerOptions.target
, but it can be any valid value so long as the final source distributions are compatible with the runtimes your library targets. See also [#ts-source-distros].
✅ DO set compilerOptions.forceConsistentCasingInFileNames
to true. forceConsistentCasingInFileNames
forces TypeScript to treat files as case sensitive, and ensures you don’t get surprised by build failures when moving between platforms.
✅ DO set compilerOptions.module
to es6
. Use a bundler such as Rollup or Webpack to produce the CommonJS and UMD builds.
✅ DO set compilerOptions.moduleResolution
to “node” if your library targets Node. Otherwise, it should be absent.
✅ DO set compilerOptions.declaration
to true. The --declaration
option tells TypeScript to emit a d.ts
file that contains the public surface area of your library. TypeScript and editors use this file to provide intellisense and type checking capabilities. Ensure you reference this type declaration file from the types
field of your package.json.
⛔️ DO NOT set compilerOptions.experimentalDecorators
to true
. The experimentalDecorators flag adds support for “v1 decorators” to TypeScript. Unfortunately the standards process has moved on to an incompatible second version that is not yet implemented by TypeScript. Taking a dependency on decorators now means signing up your users for breaking changes later.
✅ DO set compilerOptions.sourceMap
and compilerOptions.declarationMap
to true. Shipping source maps in your package ensures clients can easily debug into your library code. sourceMap
maps your emitted JS source to the declaration file and declarationMap
maps the declaration file back to the TypeScript source that generated it. Be sure to include your original TypeScript sources in the package.
✅ DO set compilerOptions.importHelpers
to true. Using external helpers keeps your package size down. Without this flag, TypeScript will add a helper block to each file that needs it. The file size savings using this option can be huge when using async
functions (as an example) in a number of different files.
TypeScript Coding Guidelines
⚠️ YOU SHOULD NOT use TypeScript namespaces. Namespaces either use the namespace
keyword explicitly, or the module
keyword with a module name (for example, module Microsoft.ApplicationInsights { ... }
). Use top-level imports/exports with ECMAScript modules instead. Namespaces make your code less compatible with standard ECMAScript and create significant friction with the TypeScript community.
⚠️ YOU SHOULD NOT use const enum
. Const enum
requires global understanding of your program to compile properly. As a result, const enum
can’t be used with Babel 7, which otherwise supports TypeScript. Avoiding const enum
will make sure your code can be compiled by any tool. Use regular enums instead.
Pagination
Most developers will want to process a list one item at a time. Higher-level APIs (for example, async iterators) are preferred in the majority of use cases. Finer-grained control over handling paginated result sets is sometimes required (for example, to handle over-quota or throttling).
✅ DO provide a list
method that returns a PagedAsyncIterableIterator
from the module @azure/core-paging
.
✅ DO provide page-related settings to the byPage()
iterator and not the per-item iterator.
✅ DO take a continuationToken
option in the byPage()
method. You must rename other parameters that perform a similar function (for example, nextMarker
). If your page type has a continuation token, it must be named continuationToken
.
✅ DO take a maxPageSize
option in the byPage()
method.
An example of a paginating client:
// usage
const client = new ServiceClient()
for await (const item of client.listItems()) {
console.log(item);
}
for await (const page of client.listItems().byPage({ maxPageSize: 50 })) {
console.log(page);
}
// implementation
interface Item {
name: string;
}
interface Page {
continuationToken: string;
items: Item[];
}
class ServiceClient {
/* ... */
listItems(): PagedAsyncIterableIterator<Item, Page> {
async function* pages () { /* ... */ }
async function* items () {
for (const page of pages()) {
for (const item of page.items) {
yield item;
}
}
}
const itemIter = items();
return {
next() {
return itemIter.next();
/* ... */
},
byPage() {
return pages();
},
[Symbol.asyncIterator]() { return this }
}
}
}
✅ DO expose non-paginated list endpoints identically to paginated list endpoints. Users shouldn’t need to appreciate the difference.
✅ DO use different types for entities returned from a list
endpoint and a get
endpoint if the returned entities have a different shape. If both entities are the same form, use the same type.
list
endpoint vs. a get
endpoint unless there’s a good reason for the difference. Using the same type for both operations will make the API surface in the client library simpler.⛔️ DO NOT expose an iterator over individual items if it causes additional service requests. Some services charge on a per-request basis. One GET
per item is often too expensive when the data isn’t used.
⛔️ DO NOT expose an API to get a paginated collection into an array. Services may return many pages, which can lead to memory exhaustion in the application.
Long Running Operations
Long-running operations are operations which consist of an initial request to start the operation followed by polling to determine when the operation has completed or failed. Long-running operations in Azure tend to follow the REST API guidelines for Long-running Operations, but there are exceptions.
✅ DO represent long-running operations with some object that encapsulates the polling and the operation status. This object, called a poller, must provide APIs for:
- querying the current operation state (either asynchronously, which may consult the service, or synchronously which must not)
- requesting an asynchronous notification when the operation has completed
- cancelling the operation if cancellation is supported by the service
- registering disinterest in the operation so polling stops
- triggering a poll operation manually (automatic polling must be disabled)
- progress reporting (if supported by the service)
✅ DO support the following polling configuration options:
pollInterval
Polling configuration may be used only in the absence of relevant retry-after headers from service, and otherwise should be ignored.
✅ DO prefix method names which return a poller with either begin
.
✅ DO provide a way to instantiate a poller with the serialized state of another poller to begin where it left off, for example by passing the state as a parameter to the same method which started the operation, or by directly instantiating a poller with that state.
⛔️ DO NOT cancel the long-running operation when cancellation is requested via a cancellation token. The cancellation token is cancelling the polling operation and should not have any effect on the service.
✅ DO log polling status at the Info
level (including time to next retry)
✅ DO expose a progress reporting mechanism to the consumer if the service reports progress as part of the polling operation. Language-dependent guidelines will present additional guidance on how to expose progress reporting in this case.
@azure/core-lro
package, which is an abstration that provides the above requirementsSupport for non-HTTP protocols
Most Azure services expose a RESTful API over HTTPS. However, a few services use other protocols, such as AMQP, MQTT, or WebRTC. In these cases, the operation of the protocol can be split into two phases:
- Per-connection (surrounding when the connection is initiated and terminated)
- Per-operation (surrounding when an operation is sent through the open connection)
The policies that are added to a HTTP request/response (authentication, unique request ID, telemetry, distributed tracing, and logging) are still valid on both a per-connection and per-operation basis. However, the methods by which these policies are implemented are protocol dependent.
✅ DO implement as many of the policies as possible on a per-connection and per-operation basis.
For example, MQTT over WebSockets provides the ability to add headers during the initiation of the WebSockets connection, so this is a good place to add authentication, telemetry, and distributed tracing policies. However, MQTT has no metadata (the equivalent of HTTP headers), so per-operation policies are not possible. AMQP, by contract, does have per-operation metadata. Unique request ID, and distributed tracing headers can be provided on a per-operation basis with AMQP.
✅ DO consult the Architecture Board on policy decisions for non-HTTP protocols. Implementation of all policies is expected. If the protocol cannot support a policy, obtain an exception from the Architecture Board.
✅ DO use the global configuration established in the Azure Core library to configure policies for non-HTTP protocols. Consumers don’t necessarily know what protocol is used by the client library. They will expect the client library to honor global configuration that they have established for the entire Azure SDK.