API Implementation
This section describes guidelines for implementing Azure SDK client libraries. Please note that some of these guidelines are automatically enforced by code generation tools.
Service Client
Service clients are the main starting points for developers calling Azure services with the Azure SDK. Each client library should have at least one client, so it’s easy to discover. The guidelines in this section describe patterns for the design of a service client.
When configuring your client library, particular care must be taken to ensure that the consumer of your client library can properly configure the connectivity to your Azure service both globally (along with other client libraries the consumer is using) and specifically with your client library.
Service Methods
Each supported language has an Azure Core library that contains common mechanisms for cross cutting concerns such as configuration and doing HTTP requests.
✅ DO use the HTTP pipeline component within Azure::Core
namespace for communicating to service REST endpoints.
HttpPipeline
The following example shows a typical way of using HttpPipeline
to implement a service call method. The HttpPipeline
will handle common HTTP requirements such as the user agent, logging, distributed tracing, retries, and proxy configuration.
HttpPipelinePolicy/Custom Policies
The HTTP pipeline includes a number of policies that all requests pass through. Examples of policies include setting required headers, authentication, generating a request ID, and implementing proxy authentication. HttpPipelinePolicy
is the base type of all policies (plugins) of the HttpPipeline
. This section describes guidelines for designing custom policies.
Some services may require custom policies to be implemented. For example, custom policies may implement fall back to secondary endpoints during retry, request signing, or other specialized authentication techniques.
✅ DO use the policy implementations in Azure::Core
whenever possible.
✅ DO review the proposed policy with the Azure SDK [Architecture Board]. There may already be an existing policy that can be modified/parameterized to satisfy your need.
✅ DO ensure thread-safety for custom policies. A practical consequence of this is that you should keep any per-request or connection bookkeeping data in the context rather than in the policy instance itself.
✅ DO document any custom policies in your package. The documentation should make it clear how a user of your library is supposed to use the policy.
Service Method Parameters
Parameter Validation
See general parameter validation guidelines.
⛔️ DO NOT validate service parameters. This includes null checks, empty strings, and other common validating conditions. Let the service validate any request parameters.
Supporting Types
Serialization
JSON Serialization
Enumeration-like Structs
As described in general enumeration guidelines, you should use enum
types whenever passing or deserializing a fixed well-known set of values to or from the service.
There may be times, however, where a struct
is necessary to capture an extensible value from the service even if well-known values are defined,
or to pass back to the service those same or other user-supplied values:
- The value is retrieved and deserialized from service, and may contain a value not supported by the client library.
- The value is roundtripped: the value is retrieved and deserialized from the service, and may later be serialized and sent back to the server.
✅ DO name enum class
es and enumerators using PascalCase.
✅ DO use enum class
for enumerations. For example:
Note that modifying the enumerators in an enumeration is considered a breaking change, if the enumerators in an enumeration may change over time, use an Extendable Enumeration instead.
Extendable Enumerations
When the set of values in an enumeration is not fixed, the Azure::Core::_internal::ExtendableEnumeration
template should be used.
The ExtendableEnumeration type contains the methods for comparison, copying, and conversion to and from string values.
Using Azure Core Types
Implementing Subtypes of Operation<T>
Subtypes of Operation<T>
are returned from service client methods invoking long running operations.
✅ DO check the value of IsDone
in subclass implementations of PollInternal
and PollUntilDoneInternal
and immediately return the result of GetRawResponse
if it is true.
✅ DO throw from methods on Operation<T>
subclasses in the following scenarios.
- If an underlying service operation call from
Poll
orPollUntilDone
throws, re-throwRequestFailedException
or its subtype. -
If the operation completes with a non-success result, throw
RequestFailedException
or its subtype fromPoll
orPollUntilDone
.- Include any relevant error state information in the exception message.
-
If the
Value
property is evaluated after the operation failed (HasValue
is false andIsDone
is true) throw the same exception as the one thrown when the operation failed. - If the
Value
property is evaluated before the operation is complete (IsDone
is false) throwTODO: What to throw
.- The exception message should be: “The operation has not yet completed.”
Azure::Core::Context
✅ DO always pass Azure::Core::Context
types by reference, preferably by const
reference.
⛔️ DO NOT cancel the input Context parameter.
The cancellation requirement stems from the nature of an Azure::Core::Context
object - there may be multiple Azure::Core::Context
objects which share the same cancellation context - cancelling one cancels all of the shared contexts. To avoid this, a service client should instead create a new Azure::Core::Context
from the original context using either Context::WithValue
, Context::WithDeadline
and cancel that new context.
SDK Feature Implementation
Configuration
✅ DO use relevant global configuration settings either by default or when explicitly requested to by the user, for example by passing in a configuration object to a client constructor.
✅ DO allow different clients of the same type to use different configurations.
✅ DO allow consumers of your service clients to opt out of all global configuration settings at once.
✅ DO allow all global configuration settings to be overridden by client-provided options. The names of these options should align with any user-facing global configuration keys.
⛔️ DO NOT change behavior based on configuration changes that occur after the client is constructed. Hierarchies of clients inherit parent client configuration unless explicitly changed or overridden. Exceptions to this requirement are as follows:
- Log level, which must take effect immediately across the Azure SDK.
- Tracing on/off, which must take effect immediately across the Azure SDK.
Configuration via Environment Variables
✅ DO prefix Azure-specific environment variables with AZURE_
.
✔️ YOU MAY use client library-specific environment variables for portal-configured settings which are provided as parameters to your client library. This generally includes credentials and connection details. For example, Service Bus could support the following environment variables:
AZURE_SERVICEBUS_CONNECTION_STRING
AZURE_SERVICEBUS_NAMESPACE
AZURE_SERVICEBUS_ISSUER
AZURE_SERVICEBUS_ACCESS_KEY
Storage could support:
AZURE_STORAGE_ACCOUNT
AZURE_STORAGE_ACCESS_KEY
AZURE_STORAGE_DNS_SUFFIX
AZURE_STORAGE_CONNECTION_STRING
✅ DO get approval from the [Architecture Board] for every new environment variable.
✅ DO use this syntax for environment variables specific to a particular Azure service:
AZURE_<ServiceName>_<ConfigurationKey>
Where ServiceName is the canonical shortname without spaces, and ConfigurationKey refers to an unnested configuration key for that client library.
⛔️ DO NOT use non-alpha-numeric characters in your environment variable names with the exception of underscore. This ensures broad interoperability.
Logging
Request logging will be done automatically by the HttpPipeline
. If a client library needs to add custom logging, follow the same guidelines and mechanisms as the pipeline logging mechanism. If a client library wants to do custom logging, the designer of the library must ensure that the logging mechanism is pluggable in the same way as the HttpPipeline
logging policy.
✅ DO follow the logging section of the Azure SDK General Guidelines if logging directly (as opposed to through the HttpPipeline
).
C++ Logging specific details
Client libraries must support robust logging mechanisms so that the consumer can adequately diagnose issues with the method calls and quickly determine whether the issue is in the consumer code, client library code, or service.
In general, our advice to consumers of these libraries is to establish logging in their preferred manner at the WARNING
level or above in production to capture problems with the application, and this level should be enough for customer support situations. Informational or verbose logging can be enabled on a case-by-case basis to assist with issue resolution.
✅ DO use the Azure Core library for logging.
✅ DO support pluggable log handlers.
✅ DO make it easy for a consumer to enable logging output to the console. The specific steps required to enable logging to the console must be documented.
✅ DO use one of the following log levels when emitting logs: Verbose
(details), Informational
(things happened), Warning
(might be a problem or not), and Error
.
✅ DO use the Error
logging level for failures that the application is unlikely to recover from (out of memory, etc.).
✅ DO use the Warning
logging level when a function fails to perform its intended task. This generally means that the function will raise an exception. Do not include occurrences of self-healing events (for example, when a request will be automatically retried).
✔️ YOU MAY log the request and response (see below) at the Warning
when a request/response cycle (to the start of the response body) exceeds a service-defined threshold. The threshold should be chosen to minimize false-positives and identify service issues.
✅ DO use the Informational
logging level when a function operates normally.
✅ DO use the Verbose
logging level for detailed troubleshooting scenarios. This is primarily intended for developers or system administrators to diagnose specific failures.
✅ DO only log headers and query parameters that are in a service-provided “allow-list” of approved headers and query parameters. All other headers and query parameters must have their values redacted.
✅ DO log request line and headers as an Informational
message. The log should include the following information:
- The HTTP method.
- The URL.
- The query parameters (redacted if not in the allow-list).
- The request headers (redacted if not in the allow-list).
- An SDK provided request ID for correlation purposes.
- The number of times this request has been attempted.
✅ DO log response line and headers as an Informational
message. The format of the log should be the following:
- The SDK provided request ID (see above).
- The status code.
- Any message provided with the status code.
- The response headers (redacted if not in the allow-list).
- The time period between the first attempt of the request and the first byte of the body.
✅ DO log an Informational
message if a service call is cancelled. The log should include:
- The SDK provided request ID (see above).
- The reason for the cancellation (if available).
✅ DO log exceptions thrown as a Warning
level message. If the log level set to Verbose
, append stack trace information to the message.
Distributed Tracing
✅ DO abstract the underlying tracing facility, allowing consumers to use the tracing implementation of their choice.
✅ DO create a new trace span for each API call. New spans must be children of the span that was passed in.
Telemetry
Client library usage telemetry is used by service teams (not consumers) to monitor what SDK language, client library version, and language/platform info a client is using to call into their service. Clients can prepend additional information indicating the name and version of the client application.
✅ DO send telemetry information in the [User-Agent header] using the following format:
[<application_id> ]azsdk-cpp-<package_name>/<package_version> <platform_info>
<application_id>
: optional application-specific string. May contain a slash, but must not contain a space. The string is supplied by the user of the client library, e.g. “XCopy/10.0.4-Preview”<package_name>
: client library (distribution) package name as it appears to the developer, replacing slashes with dashes and removing the Azure and cpp indicator. For example, “azure-security-keyvault-secrets-cpp” would specify “azsdk-cpp-security-keyvault-secrets”.<package_version>
: the version of the package. Note: this is not the version of the service<platform_info>
: information about the currently executing language runtime and OS, e.g. “MSVC/14.10.0(Windows-10-10.0.19041-SP0)”
☑️ YOU SHOULD send additional (dynamic) telemetry information as a semi-colon separated set of key-value types in the X-MS-AZSDK-Telemetry
header. For example:
X-MS-AZSDK-Telemetry: class=BlobClient;method=DownloadFile;blobType=Block
The content of the header is a semi-colon key=value list. The following keys have specific meaning:
class
is the name of the type within the client library that the consumer called to trigger the network operation.method
is the name of the method within the client library type that the consumer called to trigger the network operation.
Any other keys that are used should be common across all client libraries for a specific service. DO NOT include personally identifiable information (even encoded) in this header. Services need to configure log gathering to capture the X-MS-SDK-Telemetry
header in such a way that it can be queried through normal analytics systems.
Testing
We believe testing is a part of the development process, so we expect unit and integration tests to be a part of the source code. All components must be covered by automated testing, and developers should strive to test corner cases and main flows for every use case.
All code should contain, at least, requirements, unit tests, end-to-end tests, and samples.
Tests should be written using the [Google Test][https://google.github.io/googletest/] library.
Language-specific other
Unlike many other programming languages, which have large runtimes, the C++ standard runtime is limited in functionality and scope. The standard library covers areas such as memory and string manipulation, standard input/output, floating point and others. However, many of the features required for modern applications and services; e.g. those required for networking and advanced memory management are not part of the standard library. Instead, many of those features are included in open source C++ libraries that are also cross-platform with good support for Windows, OSX and most Linux platforms. Because of that support and because Azure SDK implementations will need such functionality, it is expected that client libraries will take dependencies on these libraries. Ensure the version matches to allow for compatibility when an application integrates multiple client libraries.
✅ DO utilize the following libraries as needed for commonly required operations:
Library | Version | Usage |
---|---|---|
libxml2 | 2.7.6 | XML parser - https://www.xmlsoft.org/ |
gtest | 1.10.0 | Google Test - https://github.com/google/googletest/ |
Note: None of the libraries in this list currently throw C++ exceptions; policy for propagation of C++ exceptions from dependencies needs to be evaluated on a dependency-by-dependency basis.
⛔️ DO NOT use implicit assignment inside a test. This is generally an accidental omission of the second =
of the logical compare. The following is confusing and prone to error.
Does the programmer really mean assignment here? Sometimes yes, but usually no. Instead use explicit tests and avoid assignment with an implicit test. The recommended form is to do the assignment before doing the test:
⛔️ DO NOT use the register keyword. Modern compilers will put variables in registers automatically.
✅ DO be const
correct. C++ provides the const
keyword to allow passing as parameters objects that cannot change to indicate when a method doesn’t modify its object. Using const
in all the right places is called “const correctness.”
✅ DO use #if
instead of #ifdef
. For example:
Someone else might compile the code with turned-of debug info like:
Alway use #if
if you have to use the preprocessor. This works fine, and does the right thing, even if DEBUG
is not defined at all (!)
If you really need to test whether a symbol is defined or not, test it with the defined()
construct, which allows you to add more things later to the conditional without editing text that’s already in the program:
✅ DO
Use #if
to comment out large code blocks.
Sometimes large blocks of code need to be commented out for testing. The easiest way to do this is with an #if 0
block:
You can’t use /**/
style comments because comments can’t contain comments and a large block of your code will probably contain connects.
Do not use #if 0
directly. Instead, use descriptive macro names:
Always add a short comment explaining why it is commented out.
⛔️ DO NOT put data definitions in header files. For example, this should be avoided:
It’s bad magic to have space consuming code silently inserted through the innocent use of header files. It’s not common practice to define variables in the header file, so it will not occur to developers to look for this when there are problems. Instead, define the variable once in a source file and then use an extern
statement to reference it in the header file.
⛔️ DO NOT use magic numbers. A magic number is a bare naked number used in source code. It’s magic because no-one will know what it means after a while. This significantly reduces maintainability. For example:
Instead of magic numbers, use a real name that means something. You can use constexpr
for names. For example:
✅ DO check every system call for an error return, unless you know you wish to ignore errors. For example, printf
returns an error code but it is rarely relevant. Cast the return to (void) if you do not care about the error code.
✅ DO include the system error text when reporting system error messages.
☑️ YOU SHOULD follow the CPP Core Guidelines whenever possible, with the following exceptions:
- List TBD.
Complexity Management
☑️ YOU SHOULD Initialize all variables. Only leave them uninitialized if there is a real performance reason to do so. Use static and dynamic analysis tools to check for uninitialized access. You may leave “result” variables uninitialized so long as they clearly do not escape from the innermost lexical scope.
☑️ YOU SHOULD limit function bodies to one page of code (40 lines, approximately).
✅ DO document null statements. Always document a null body for a for
or while
statement so that it is clear the null body is intentional.
✅ DO use explicit comparisons when testing for failure. Use if (FAIL != f())
rather than if (f())
, even though FAIL may have the value 0 which C considers to be false. An explicit test will help you out later when somebody decides that a failure return should be -1 instead of 0.
⛔️ DO NOT compare boolean values with explicit comparisons. Rather than writing if (f() == true)
, write if (f())
- the result is clearer and more succinct. The same applies to objects that implement operator bool()
Explicit comparison should be used even if the comparison value will never change. e.g. if (!(bufsize % sizeof(int)))
should be written as if (0 == (bufsize % sizeof(int))
to reflect the numeric (not boolean) nature of the test.
A frequent trouble spot is using strcmp
to test for string equality. You should never use a default action. The preferred approach is to use an inline function:
~ Should
⚠️ YOU SHOULD NOT use embedded assignments. There is a time and a place for embedded assignment statements. In some constructs, there is no better way to accomplish the results without making the code bulkier and less readable.
However, one should consider the tradeoff between increased speed and decreased maintainability that results when embedded assignments are used in artificial places.
Templates
✅ DO name function templates and class templates the same as one would name non-templates.
✅ DO name template arguments with PascalCase.
Macros
☑️ YOU SHOULD avoid use of macros. It is acceptable to use macros in the following situations. Use outside of these situations should contact the Azure Review Board.
- Platform, compiler, or other environment detection (for example,
_WIN32
or_MSC_VER
). - Emission or suppression of diagnostics.
- Emission or supression of debugging asserts.
- Import declarations. (
__declspec(dllimport)
,__declspec(dllexport)
)
✅ DO name macros with ALL_CAPITAL_SNAKE_CASE.
✅ DO prepend macro names with AZ_<SERVICENAME>
to make macros unique.
⛔️ DO NOT use macros where an inline function or function template would achieve the same effect. Macros are not required for code efficiency.
⛔️ DO NOT change syntax via macro substitution. It makes the program unintelligible to all but the perpetrator.
Type Safety Recommendations
✅ DO In class types, implement the “rule of zero”, the “rule of 3”, or the “rule of 5”. That is, of the special member functions, a type should implement exactly one of the following:
- No copy constructor, no copy assignment operator, no move constructor, no move assignment operator, or destructor.
- A copy constructor, a copy assignment operator, no move constructor, no move assignment operator, and a destructor.
- A copy constructor, a copy assignment operator, a move constructor, a move assignment operator, and a destructor.
This encourages use of resource managing types like std::unique_ptr (which implements the rule of 5) as a compositional tool in more complex data models that implement the rule of zero.
✅ DO provide types which are usable when default-initialized. (That is, every constructor must initialize all type invariants, not assume members have default values of 0 or similar.)
✅ DO define getters and setters for data transfer objects. Expose the members directly to users unless you need to enforce some constraints on the data. For example:
✅ DO declare variables in structures organized by use in a manner that minimizes memory wastage because of compiler alignment issues and size. All things being equal, use alphabetical ordering.
⛔️ DO NOT use enums to model any data sent to the service. Use enums only for types completely internal to the client library. For example, an enum to disable Nagle’s algorithm would be OK, but an enum to ask the service to create a particular entity kind is not.
Const and Reference members
⛔️ DO NOT declare types with const or reference members. Const and reference members artificially make your types non-Regular as they aren’t assignable, and have surprising interactions with C++ Core language rules. For example, many accesses to const or reference members would need to involve use of std::launder
to avoid undefined behavior, but std::launder
was added in C++17, a later version than the SDKs currently target. See C++ Core Working Group CWG1116 “Aliasing of union members”, CWG1776 “Replacement of class objects containing reference members”, and P0137R1 “Replacement of class objects containing reference members” for additional details.
If you want a type to provide effectively const data except assignment, declare all your member functions const. Const member functions only get a const view of the class’ data.
Parameter passing rules
In general, all method parameters that are not POD should be passed by const reference
.
Integer sizes
The following integer rules are listed in rough priority order. Integer size selections are primarily driven by service future compatibility. For example, just because today a service might have a 2 GiB file size limit does not mean that it will have such a limit forever. We believe 64 bit length limits will be sufficient for sizes an individual program works with for the foreseeable future.
✅ DO Represent file sizes with std::int64_t
, even on 32 bit platforms.
✅ DO Represent memory buffer sizes with std::size_t
or std::ptrdiff_t
as appropriate for the environment. Between the two, choose the type likely to need the fewest conversions in application. For example, we would prefer signed std::ptrdiff_t
in most cases because signed integers behave like programmers expect more consistently, but the SDK will need to transact with malloc
, std::vector
, and/or std::string
which all speak unsigned std::size_t
.
✅ DO Represent any other integral quantity passed over the wire to a service using std::int64_t
, even if the service uses a 32 bit constant internally today.
⛔️ DO NOT Use int
under any circumstances, including for
loop indexes. Those should usually use ptrdiff_t
or size_t
under the buffer size rule above.
✔️ YOU MAY Use any integer type in the intN_t
or uintN_t
family as appropriate for quantities not enumerated above, such as calculated values internal to the SDK such as retry counts.
Secure functions
⚠️ YOU SHOULD NOT use Microsoft security enhanced versions of CRT functions to implement APIs that need to be portable across many platforms. Such code is not portable and is not compatible with either the C or C++ Standards. See arguments against.
Enumerations
✅ DO use enum
or enum class
for values shared “over the wire” with a service, to support future compatibility with the service where additional values are added. Such values should be persisted as strings in client data structures instead.
✔️ YOU MAY provide an ‘extensible enum’ pattern for storing service enumerations which provides reasonable constant values. This pattern stores the value as a string but provides public static member fields with the individual values for customer consumption. For example:
Physical Design
☑️ YOU SHOULD include files using quotes (“) for files within the same git repository, and angle brackets (<>) for external dependencies.
☑️ YOU SHOULD declare all types that are only used within the same .cpp
file in an unnamed namespace. For example:
Class Types (including union
s and struct
s)
Throughout this section, class types includes types with class-key struct
or class-key union
, consistent with the C++ Standard.
✅ DO name class types with PascalCase.
✅ DO name public
and protected
member variables with PascalCase.
✅ DO name private
member variables with an m_
prefix, followed by a camelCase name. For example, m_timeoutMs
.
✅ DO name member functions with PascalCase, except where the C++ Standard forbids this. For example, UploadBlob
, or operator[]
.
☑️ YOU SHOULD declare classes with only public members using class-key struct
.
☑️ YOU SHOULD define class types without using typedef
s. For example:
By convention, C++ Structs ⚠️ YOU SHOULD NOT define any methods. If a C++ object needs methods, it should be declared as a class
.
Tooling
We use a common build and test pipeline to provide for automatic distribution of client libraries. To support this, we need common tooling.
✅ DO use CMake v3.7 for your project build system.
Version 3.7 is the minimum version installed on the Azure Pipelines Microsoft hosted agents (https://docs.microsoft.com/azure/devops/pipelines/agents/hosted)
✅ DO include the following standard targets:
build
to build the librarytest
to run the unit test suitedocs
to generate reference documentationall
to run all three targets
Include other targets as they appear useful during the development process.
✅ DO use hidden visibility when building dynamic libraries. For CMake:
This allows you to use an export macro to export symbols. For example:
CMake will automatically generate an appropriate export header:
✅ DO use clang-format for formatting, with the following command-line options:
Using -i
does an in-place edit of the files for style. There is a Visual Studio extension that binds Ctrl-R Ctrl-F to this operation. Visual Studio 2019 includes this functionality by default.
✅ DO generate API documentation with doxygen
.
For example in CMake:
Notice that:
- We use
find_package()
to find doxygen - We use the
DOXYGEN_<PREF>
CMake variables instead of writing your owndoxyfile
. - We set
OPTIMIZE_OUTPUT_FOR_C
in order to get more C appropriate output. - We use
doxygen_add_docs
to add the target, this will generate adoxyfile
for you.
✅ DO provide a CMake option of the form <SDK_NAME>_BUILD_SAMPLES
that includes all samples in the build. For example:
⛔️ DO NOT install samples by default.
Supported platforms
✅ DO support at least the following platforms and associated compilers when implementing your client library:
- Windows
- Unix-like operating systems (Linux, Unix, etc)
- OSX/iOS
As of 10/2024, the Azure SDK for C++ is currently tested against the following platforms:
Operating System | Versions | Architectures | Compiler | Compiler Version | Notes |
---|---|---|---|---|---|
Windows | 2019, 2022 | x32, x64 | MSVC | MSVC 16, MSVC 17 | |
OSX | 14 | x64 | XCODE | 15.4 | |
Linux - Ubuntu | 2020.04, 2022.04 | x64 | GCC | 8, 9 | |
Linux - Ubuntu | 2020.04, 2022.04 | x64 | clang | 11, 13, 15 |
For a current list of supported platforms, see the platform matrix.
☑️ YOU SHOULD support the following additional platforms and associated compilers when implementing your client library.
⚠️ YOU SHOULD NOT use compiler extensions. Examples of extensions to avoid include:
Use the appropriate options for each compiler to prevent the use of such extensions.
✅ DO use compiler flags to identify warnings:
Compiler | Compiler Flags |
---|---|
gcc | -Wall -Wextra |
cpp and XCode | -Wall -Wextra |
MSVC | /W4 |