Skip to content

Common components

In general, APIs should be designed to be self-contained. APIs generally need to be able to move forward independently of one another, and mutual dependencies can cause downstream APIs to be forced into taking major version changes or even lead to dependency conflicts.

However, there are also cases where common structures are valuable, especially where a concept is well-known and it is sufficiently clear that it will not change. Having a single representation of these common structures is valuable because it avoids disrepancies between APIs in things like how dates or monetary values are represented. It also enables shared libraries for common operations, such as basic arithmetic on monetary values.

Common components serve this use case.

Guidance

The public representation of APIs should be self-contained (for protocol buffers, this means that all API protos used by the API originate in the same proto package), except for common components, which may be used freely in any API.

An API must not define a set of API-specific common components which live outside of its versioning structure. This prevents independent movement of particular versions and also causes problems for client libraries in many languages that compile protobuf messages into classes.

An API should not define alternative representations of any of the existing common components described below, even within its versioning structure.

Existing common components

The common components, which public-facing APIs may safely depend on, are defined canonically in the AEP common components repository. These include READMEs with canonical definitions for each component, and — when applicable — implementations of these definitions, in both JSON Schema and protobuf formats. The protobufs are also published to the Buf Schema Sepository at buf.build/aep, and the JSON schemas are published to the JSON Schema Store with names beginning with aep-, for example aep-type-money or aep-longrunning-operation.

While the AEP common components repository is canonical and takes precedence over this list, some of the common components defined there include representations of the following concepts:

API design patterns

  • Operation: Represents the status of a long-running request (see AEP-151 for details).

gRPC-specific API design patterns

  • google.api.* (but not subpackages of google.api): Annotations useful for gRPC/JSON transcoding, supported by frameworks including .NET 7.

  • google.rpc.*: A small number of components related to gRPC request/response status and errors.

Common types

This section provides examples for the sake of illustration; it may not be exhaustive. The AEP common components repo is canonical.

General common types

  • Date: A calendar date, with no time or time zone component.

  • DateTime: A calendar date and wall-clock time, with optional time zone or UTC offset information.

  • DayOfWeek: An enumeration representing the day of the week.

  • Duration: A duration with nanosecond-level precision.

  • Interval: An interval between two timestamps.

  • Month: An enumeration representing the Gregorian month.

  • TimeOfDay: Wall-clock time, with no date or time zone component.

  • Timestamp: A timestamp with nanosecond-level precision.

Protobuf well-known types

The google.protobuf package is shipped with protocol buffers itself, rather than with API tooling. The Well-Known Types defined in this package should always be used when appropriate, and the AEP common components repo does not define any protos for these types, even when it defines a corresponding JSON Schema. These include:

  • google.protobuf.Duration: Durations, with nanosecond-level precision. The protobuf runtime provides helper functions to convert to and from language-native duration objects where applicable (such as Python’s timedelta).
  • google.protobuf.Timestamp: Timestamps, with nanosecond-level precision. The protobuf runtime provides helper functions in most languages to convert to and from language-native timestamp objects (such as Python’s datetime).

google.protobuf also provides some useful components that correspond to JSON primitives (and so have no representation at all in the AEP common components repo), namely:

  • google.protobuf.Value: An arbitrary JSON value. The protobuf runtime provides helper functions in most languages to convert Value objects to and from JSON.
  • google.protobuf.Struct: JSON-like structures (a dictionary of primitives, lists, and other dictionaries). The protobuf runtime provides helper functions in most languages to convert Struct objects to and from JSON.

google.protobuf.Struct and google.protobuf.Value are designated common components by this AEP; proto-based APIs should use them when representing arbitrary JSON-like structures.

Libraries for common types

For the common components in the aep.type namespace, which represent common data types, the AEP common components repo may contain canonical libraries in a number of languages. These libraries are designed to be idiomatic in a given language, and should feel similar to using the language’s standard libraries. They should provide basic functionality like adding two Money values, or determining if one Date comes before another.

When a language already has a standard library representation of a common type (such as Python’s datetime for the Timestamp type), there may instead be a library for converting the JSON or protobuf representation to the standard library representation.

If a library you want does not exist, and you want to contribute one, please open an issue on the AEP common components repository in GitHub.

Appendix: Adding to common components

Occasionally, it may be useful to add protos to these packages or to add to the list of common components. In order to do this, open an issue on the AEP common components repository in GitHub.

However, some general guidelines are worth noting for this:

  • Schemas should only be granted common component status if it is certain that they will never change (at all — even in ways that would normally be considered backwards compatible). Common components are not versioned, and it must be the case that API creators and consumers can rely on the component to be a complete and accurate representation indefinitely.
  • Schemas must be applicable to a significant number of APIs for consideration as common components.
  • Even after a common component is added, APIs using local versions must continue to do so until they go to the next major version.