Chapter 8

Evolve APIs

This chapter covers patterns eligible when APIs change over time. Most successful APIs do evolve; compatibility and extensibility are somewhat conflicting requirements that have to be balanced during the API life cycle. Clients and providers might not agree on the most efficient mix. Keeping multiple versions supported is costly; full backward compatibility might be desired. However, it is usually harder to achieve in practice as it seems. Poor evolution decisions might disappoint customers (and their API clients) and stress providers (and their developers).

We first motivate the need for evolution patterns and then present those we found in practice: two patterns for versioning and compatibility management and four patterns describing different life-cycle management guarantees.

This chapter corresponds to the Refine phase of the Align-Define-Design-Refine (ADDR) process.

Introduction to API Evolution

By definition, an API is not a static standalone product but part of an open, distributed, and interconnected system. APIs are intended to provide a rock-solid foundation on which to build client applications. Still, over long periods of time, like ocean waves that shape the rocky cliffs above them, APIs do change, especially when they are used by wave after wave of clients.

As APIs evolve to adapt to a changing environment, new features are added, bugs and defects get fixed, and some features are discontinued. Our evolution patterns help introduce API changes in a controlled way, deal with their consequences,

and manage the impact of these changes on API clients. They support API owners, designers, and their customers when answering the following question:

What are the governing rules balancing stability and compatibility with maintainability and extensibility during API evolution?

Challenges When Evolving APIs

Our evolution patterns are concerned with the following desired qualities directly or indirectly:

  • Autonomy: Allowing the API provider and the client to follow different life cycles; a provider can roll out new API versions without breaking existing clients.

  • Loose coupling: Minimizing the impact on the client forced by API changes.

  • Extensibility: Making it possible for the provider to improve and extend the API and change it to accommodate new requirements.

  • Compatibility: Guaranteeing that API changes do not lead to semantic “misunderstandings” between client and provider.

  • Sustainability: Minimizing the long-term maintenance effort to support old clients.

The different and independent life cycles, deployment frequencies, and schedules of providers and clients of APIs make it necessary to plan API evolution early and then continuously. They forbid making arbitrary changes to an already published API. This problem becomes more severe as an increasing number of clients start to use and depend on the API. The provider’s influence on the clients or ability to manage them may shrink if many clients exist (or the provider does not know its clients). Public APIs are particularly challenging to evolve: if alternative providers exist, clients might prefer the most stable API being offered. However, even if no competing provider is available, API clients might not be able to adapt to new API versions at all and thus rely on the provider to evolve the API fairly. This is especially the case if clients are known to have been implemented in a project by some contracted developer who is not available anymore. For example, a small company might have paid an external consultant to integrate their online shop with a payment provider via a payment API. By the time the API moves to a new version, this external consultant might have moved on to another assignment.

Compatibility and extensibility are typically conflicting quality requirements. Many considerations in the evolution of an API are driven by compatibility considerations. Compatibility is a property of the relation between a provider and a client. The two parties are compatible if they can conduct their message exchange and correctly interpret and process all messages according to the semantics of the respective API version. For instance, the provider of API version n and clients written for this version are compatible by definition (assuming the interoperability tests have passed). If the client for API version n is compatible with the API provider for version n-1, the provider is forward-compatible with the new client version. If the client for API version n is compatible with the provider version n+1, the provider is backward-compatible with the old API version.

Compatibility is easy to achieve—or at least assumed to exist—at the time of the initial deployment of an API provider and its clients. An initial version exists and is documented in the API DESCRIPTION, so that API clients and providers agree on and share the same knowledge; interoperability can be designed and tested for. While the API evolves, the shared understanding may begin to vaporize; clients and provider drift apart simply because only one side may actually get a chance to change.

Compatibility considerations become more relevant and more difficult to achieve when the life cycle of all API providers and all clients can no longer be synchronized. With the move of many applications to cloud computing, the number of remote clients has increased significantly and client-provider relationships keep changing dynamically. In modern architecture paradigms such as microservices, an important characteristic is the ability to scale independently (to run multiple instances of a service at the same time, that is), and to deploy new versions with zero downtime. The latter is achieved by having multiple service instances running at the same time and switching them to the new version one after another until all instances have been upgraded. At least during such transition time, multiple versions of the API are offered. This means that when designing how to evolve an API and guaranteeing its compatibility, the possibility of having multiple client versions interact with multiple API versions must be taken into account.

Extensibility is the ability to offer new functionality in an API. For example, the current version of an API may expose response messages that contain a single DATA ELEMENT (Chapter 6, “Design Request and Response Message Representations”) called “price” representing money amounts, and the API DESCRIPTION may explain that the currency of the price is US dollar. In a future version of the API, multiple currencies shall be supported. Implementing such an extension can easily break existing clients because they can handle only US dollar values; if a new METADATA ELEMENT called “currency” is introduced, they will not be able to process it until they are updated. Thus, extensibility sometimes conflicts with maintaining compatibility.

The evolution patterns in this chapter are concerned with conscious decisions about the level of commitment and life-cycle support and with keeping or breaking compatibility under different circumstances. They also describe how to communicate breaking and nonbreaking changes.

Differing life cycles, deployment frequencies, and deployment dates of providers and clients occur frequently in practice, especially in PUBLIC API and COMMUNITY API scenarios (two patterns featured in Chapter 4, “Pattern Language Introduction”). This makes it necessary to plan API evolution before any software releases because it is difficult—or sometimes even impossible—to change an already published API arbitrarily. Depending on the ratio of API providers and clients, it is worthwhile to burden the provider (for maintaining older API versions) or the client (for migrating to new API versions more often). Political factors—such as how important a customer is—influence the solution space: to avoid losing dissatisfied clients, the provider will invest more effort to support old API versions. In case of a more powerful position of the provider, clients can be forced to migrate to newer API versions more often by shortening support periods of APIs or features within an API.

Sometimes APIs are released without a strategy for maintaining and updating them. Such an ad hoc approach leads to problems down the road resulting in broken clients and unavailability of those clients to their users. Even worse, problems may go unnoticed when no measures are taken to prevent clients from misinterpreting a message, which—while keeping an identical or at least similar syntax—has changed semantics in a new API version (for instance, does a price element include value added tax or not, and what is the rate?). Versioning an entire API with all its endpoints, operations, and messages is a rather coarsegrained strategy and leads to many—perhaps too many—released versions. Much effort may have to be spent on the client side to track and adapt a new API version.

If no explicit guarantees are given, clients often implicitly expect an API to be offered forever (which, in most cases, is not what a provider wants). When an API eventually sunsets, but the client expected it to be available for eternity (especially in the case of PUBLIC APIS with anonymous clients) or perhaps even had negotiated for lifetime extensions, the provider’s reputation is damaged.

Sometimes providers want to change versions too often. This can result in problems with maintaining multiple versions in parallel or forcing upgrades upon their clients. It is important to avoid customer churn from too many versions that do not add enough value to invest the time and resources to upgrade. Moreover, some APIs must assume that API client developers are not available all the time; in that case, the API provider often has to support previous versions of its API. Such a situation occurs, for example, if client developers were hired for a small business Web site that must keep running, and they will never be paid to upgrade to a new API version. The Stripe API [Stripe 2022] for accepting online payments might have such small business clients. Student term projects often use public APIs; breaking changes to these APIs cause the project results to stop working, often after the projects ended.

Patterns in This Chapter

Figure 8.1 shows the pattern map for this chapter.

Images

Figure 8.1 Pattern map for this chapter (API evolution)

Introducing an explicit VERSION IDENTIFIER that is visible to and correctly validated by message recipients helps clients and providers distinguish compatible and incompatible changes; such an identifier is also useful for API monitoring and support. A three-part VERSION IDENTIFIER following the SEMANTIC VERSIONING pattern describes the compatibility between changes and thus conveys more information than simple version numbers.

LIMITED LIFETIME GUARANTEE imposes a timeframe in which an API is supported. This time is communicated when the API is published, allowing clients to plan necessary API migrations in a timely manner. When the TWO IN PRODUCTION pattern is applied, providers offer multiple versions of an API to avoid semantic misunderstandings that are based on poorly implemented backward or forward compatibility and give clients freedom of choice; each version running in production has a separate VERSION IDENTIFIER. This is a middle-ground solution allowing for a gradual transition. Sometimes an API provider explicitly does not want to make any guarantees, for example, when developing an API and still fine-tuning its exact message structure and endpoint design. In such situations, the EXPERIMENTAL PREVIEW pattern can be used, which gives no guarantees but allows future clients to learn about and experiment with the API under development. With AGGRESSIVE OBSOLESCENCE, an API provider can deprecate and phase out APIs (or parts of them) at any time without offering multiple API versions at once.

Versioning and Compatibility Management

The two patterns in this section are VERSION IDENTIFIER and SEMANTIC VERSIONING.

Images Pattern: VERSION IDENTIFIER

When and Why to Apply

An API that runs in production evolves. New versions with improved functionality are offered over time. Eventually, the changes in a new version are no longer backward compatible; this causes existing clients to break.

How can an API provider indicate its current capabilities as well as the existence of possibly incompatible changes to clients in order to prevent malfunctioning of clients due to undiscovered interpretation errors?

  • Accuracy and exact identification: When releasing a new API version, no problems with semantic mismatches or other differences between the new and the older API versions should occur. Clients must be able to rely on the syntax and semantics of an API version, even if that version is improved, extended, or otherwise modified.

  • No accidental breakage of compatibility: If versions are called out in the messages to and from the API, communication participants can reject a request or response if they are confronted with an unknown or incompatible version number. This way, it is impossible to break backward compatibility accidentally, which might happen when the semantics of an existing representation element change without notice.

  • Client-side impact: Breaking changes to an API require changes to the clients. These changes usually do not add any business value. Therefore, clients appreciate a stable API that they can rely on, an API that does not cause hidden costs for frequent maintenance releases that keep up with API changes.

  • Traceability of API versions in use: API providers can monitor which and/or how many clients depend on a specific API version. This data can be used to plan further governance actions, for instance, retiring old API versions or prioritizing feature enhancements.

Sometimes organizations roll out APIs without planning how to manage their life cycles. They might think that such planning can be just as effectively done after rollout. However, lack of governance and versioning is one of the factors that have caused some service-oriented architecture initiatives and projects to fail in the past [Joachim 2013].

How It Works

Introduce an explicit version indicator. Include this VERSION IDENTIFIER in the API DESCRIPTION and in the exchanged messages. To do the latter, add a METADATA ELEMENT to the endpoint address, the protocol header, or the message payload.

The explicit VERSION IDENTIFIER often takes a numeric value to indicate evolution progress and maturity. It can be included in dedicated representation elements, attribute/element name suffixes, parts of the endpoint addresses such as URLs, domain names, XML namespaces, or the HTTP content type header. To avoid consistency issues, the VERSION IDENTIFIER should appear in one and only one place in all message exchanges supported by an API, unless clients or middleware strongly desire to see it in several places.

To mint identifiers, the three-part SEMANTIC VERSIONING pattern is frequently used. By referring to such a structured VERSION IDENTIFIER, communication parties can check whether they can understand and correctly interpret the message; incompatibilities are straightforward to spot and distinguish from feature extensions.

By indicating a new version with a different VERSION IDENTIFIER, the receiving party can abort the interpretation of the message before any further problems occur and report an incompatibility error (for example, in an ERROR REPORT). The API DESCRIPTION can reference features that were introduced at a particular point in time (such as at the release of a certain version) or that are available only in certain

API versions but have been decommissioned in later versions, for example, when using the AGGRESSIVE OBSOLESCENCE pattern.

Note that the schema of request and response message (for instance, defined as custom media types in HTTP resource APIs) can also be versioned, possibly only loosely aligned with the endpoint/operation versioning. Alexander Dean and Frederick Blundun call this approach SchemaVer in [Dean 2014].

Also note that API evolution and implementation evolution are two different things, as the implementation can evolve separately from the interface (and be updated more frequently). This might lead to the use of multiple version identifiers, one for the remote API and one for each implementation of it.

All implementation dependencies should be included in the versioning concept (and/or backward compatibility of the dependencies must be ensured): if lower-layer components such as a database that underpins stateful API calls have a schema that cannot be evolved as fast as the API itself, this might slow down the release frequency. It must be clear which of the two (or more) API versions in production use which version of backend systems and other downstream dependencies. A “roll forward” strategy, or adding a facade that decouples implementation versioning from API versioning, may be considered.

Example

In HTTP resource APIs, the version of different features can be indicated as follows. The version of specific representation formats supported by the client appears in the content-type negotiation headers of HTTP such as the Accept header [Fielding 2014c]:

GET /customers/1234
Accept: text/json+customer; version=1.0
...

The version of specific endpoints and operations becomes part of the resource identifier:

GET v2/customers/1234
...

The version of the whole API can also be specified in the host domain name:

GET /customers/1234
Host: v2.api.service.com
...

In SOAP/XML-based APIs, the version is usually indicated as part of the namespace of the top-level message element:

<soap:Envelope>
  <soap:Body>
    <ns:MyMessage xmlns:ns="http://www.nnn.org/ns/1.0/">
    ...
    </ns:MyMessage>
  </soap:Body>
</soap:Envelope>

Another possibility is to specify the version in the payload, as in the following JSON example. In the initial version 1.0 of the billing API, the prices were defined in euros:

{
  "version": "1.0",
  "products": [
    {
      "productId": "ABC123",
      "quantity": 5;
      "price": 5.00;
    }
  ]
}

With a new version, the requirement of multicurrency was realized. This leads to the new data structure and the new contents of the version element "version": "2.0":

{
  "version": "2.0",
  "products": [
    {
      "productId": "ABC123",
      "quantity": 5;
      "price": 5.00;
      "currency": "USD"
    }
  ]
}

If no VERSION IDENTIFIER or any other mechanism to indicate a breaking change had been used, old software interpreting the version 2.0 of the message would assume that the product costs five euros, although it costs five US dollars. This is because a new attribute has changed the semantics of an existing one. Passing the version in the HTTP content type, as shown earlier, can eliminate this problem. While it would be possible to introduce a new field, priceInDollars, to avoid this problem, such changes lead to technical debt, which aggregates over time, especially in less trivial examples.

Discussion

Use of the VERSION IDENTIFIER pattern allows the provider to clearly communicate API, endpoint, operation, and message compatibility and extensibility. It reduces the likelihood of problems due to undetected semantic changes between API versions accidentally breaking compatibility. It also enables tracing which message payload versions are actually used by clients.

APIs that work with LINK IDENTIFIERS, such as hypermedia controls in HTTP resource APIs, require special attention when versioning them. Endpoints and APIs that are rather tightly coupled with each other, such as those forming an API product, should be versioned together (in a coordinated fashion, that is). APIs that are rather loosely coupled, such as those exposed by microservices owned and maintained by different teams in an organization, are more challenging to evolve. In the Lakeside Mutual case, if version 5 of the Customer Management backend API returns LINK IDENTIFIERS that refer to policy INFORMATION HOLDER RESOURCES residing in the Policy Management backend, which policy endpoint version does Customer Management backend assume? It may not know the Policy Management backend API version that the API client receiving the policy links, which reside in the Customer Self-Service frontend, is capable of handling (the mentioned components of the Lakeside Mutual architecture were introduced in Figure 2.6).

When the VERSION IDENTIFIER changes, clients might be required to migrate to a new API version even though the functionality that they rely on has not changed; this increases the effort for some API clients.

Introducing VERSION IDENTIFIERS does not allow providers to make arbitrary changes to the API, nor does it minimize the changes necessary to support old clients. However, it makes it possible to apply patters that have these benefits, for example, when offering TWO IN PRODUCTION. The pattern itself does not support the decoupling of the provider and client life cycle, either, but it is required by other patterns that do so. For example, implementing TWO IN PRODUCTION and AGGRESSIVE OBSOLESCENCE is simplified when versions are explicitly signaled.

This pattern describes a simple but effective mechanism to indicate breaking changes, especially those changes that a “Tolerant Reader” [Daigneau 2011] would be able to parse successfully but would fail to understand and use correctly. By making the version explicit, providers can force the client to reject a message of a newer version or can refuse to process an outdated request. This provides a mechanism to make incompatible changes safely. However, it forces the clients to migrate to a new, supported API version. Patterns like TWO IN PRODUCTION can provide a grace period in which clients can migrate to the new version.

When opting for explicit versioning, it must be decided on what level the versioning takes place: in a Web Services Description Language (WSDL), the whole contract can be versioned (by changing the namespace, for example), as can its individual operations (for instance, by having a version suffix) and representation elements (schemas). HTTP resource APIs can also be versioned differently: for example, content types, URLs, and version elements in the payload can be used to indicate the version (as shown earlier). Versioning scope (“subject”) and versioning solution (“means”) should not be confused; for instance, representation elements can carry version information but also be subject to versioning themselves.

Using smaller units of versioning, for instance, single operations, decreases coupling between provider and client: consumers might use only features of an API endpoint that are not impacted by a change. Instead of providing a separate API endpoint per client, fine-grained versioning (on the level of operations or message representation elements) can limit the change impact. However, the more elements of an API are versioned, the higher the governance and test effort is. Both provider and client organizations need to keep track of the possibly many versioned elements and their active versions. Offering APIs specialized for special clients or different client types might be a better design choice in such circumstances.

Using a VERSION IDENTIFIER can lead to unnecessary change requests for software components such as the API client. This may happen if the code needs to be changed whenever the API version is changed. For example, changes to an XML namespace require changes and new deployments of clients. When using primitive code generation (for instance, JAXB [Wikipedia 2022f] without any customizations), this can be a problem because a change in the namespace results in a change in the Java package name, which affects all generated classes and references in the code to those classes. At a minimum, the code generation should be customized (or more robust and stable mechanisms to access the data be used) so that the impact of such technical changes is reduced and contained.

Different integration technologies offer different mechanisms for versioning and have different corresponding practices that are accepted by the respective communities. If SOAP is used, versions are usually indicated by different namespaces of the exchanged SOAP messages, although some APIs use a version suffix to the top-level message element. In contrast, parts of the REST community condemn the use of explicit VERSION IDENTIFIERS, and others encourage the use of HTTP accept and content-type headers (for instance, see [Klabnik 2011]) to convey the version. However, in practice, many applications also use VERSION IDENTIFIERS in the exchanged JSON/XML or the URL to indicate the version.

Related Patterns

A VERSION IDENTIFIER can be seen as a special type of METADATA ELEMENT. The VERSION IDENTIFIER can be further structured, for example, by using the SEMANTIC VERSIONING pattern. The life-cycle pattern TWO IN PRODUCTION requires explicit versioning; the other life-cycle patterns (AGGRESSIVE OBSOLESCENCE, EXPERIMENTAL PREVIEW, LIMITED LIFETIME GUARANTEE) may also use it.

The visibility and role of an API drive related design decisions. For instance, the different life cycles, deployment frequencies, and release dates of providers and clients in a PUBLIC API for FRONTEND INTEGRATION scenario might make it necessary to plan API evolution before making design decisions. Such scenarios usually do not allow providers to make arbitrary ad hoc changes to already published APIs due to the impact of such changes on clients (for example, downtimes, resultant test and migration effort); some or all of these clients might not even be known. A COMMUNITY API providing BACKEND INTEGRATION capabilities between a few stable communication parties that are maintained on the same release cycle (and share a common roadmap) might be able to employ more relaxed versioning tactics. Finally, a SOLUTION-INTERNAL API for FRONTEND INTEGRATION connecting a mobile app frontend with a single backend owned, developed, and operated by the same agile team might fall back to an ad hoc, opportunistic approach to evolution that relies on frequent, automated unit and integration tests within a continuous integration and delivery practice.

More Information

Because versioning is an important aspect of API and service design, there is much discussion about it in different development communities. The strategies differ widely and are debated passionately. Opinions reach from no explicit versioning at all because an API should always be backward compatible, according to “Roy Fielding on Versioning, Hypermedia, and REST” [Amundsen 2014], to the different versioning strategies compared by Mark Little [Little 2013]. James Higginbotham describes the available options in “When and How Do You Version Your API?” [Higginbotham 2017a] and in Principles of Web API Design [Higginbotham 2021].

Chapter 11 of SOA in Practice [Josuttis 2007] introduces a service life cycle in the context of service-oriented architecture (SOA) design; Chapter 12 discusses versioning.

Chapter 13 of Build APIS You Won’t Hate [Sturgeon 2016b] discusses seven options for versioning (with the VERSION IDENTIFIER in URLs being one of these options) and their advantages and disadvantages. It also gives implementation hints.

Chapter 15 of SOA with REST [Erl 2013] deals with service versioning for REST.

Images Pattern: SEMANTIC VERSIONING

When and Why to Apply

When adding VERSION IDENTIFIERS to request and response messages or publishing version information in the API DESCRIPTION, it is not necessarily clear from a single number how significant the changes between different versions are. As a consequence, the impact of these changes is unknown and has to be analyzed by every client (for instance, by inspecting the API documentation in depth or running special compatibility tests). Consumers would like to know the impact of a version upgrade beforehand so that they can plan the migration without having to invest much effort or take unnecessary risk. In order to fulfill any guarantees made to clients, providers must manage different versions and thus have to unveil and disclose whether the planned API interface and implementation changes are compatible or will break client functionality.

How can stakeholders compare API versions to detect immediately whether they are compatible?

  • Minimal effort to detect version incompatibility: When APIs change, it is important for all parties, especially the clients, to know what the impact of the rollout of the new version is. Clients want to know what level of compatibility is achieved so that they can decide whether to simply use the new version straight away or to plan and execute a necessary migration.

  • Clarity of change impact: Whenever a new API interface version is released, it should be clear to the developers of the API provider as well as of the API clients what the change impact and the guarantees—especially with regard to compatibility—are. To plan API client development projects, the effort and risk of accommodating the new API version have to be known.

  • Clear separation of changes with different levels of impact and compatibility: In order to make the change impact clear and address the needs of different clients, it is often necessary to separate changes with different levels of backward compatibility. For example, many implementation-level bug fixes can be accomplished with backward compatibility preserved; fixing design bugs or closing conceptual gaps often requires breaking changes in the clients.

  • Manageability of API versions and related governance effort: Managing an API, and especially multiple API versions, is difficult and binds resources. The more guarantees have been made to the clients and the more APIs and API versions are made available, the higher the effort for managing those APIs usually is. Providers usually strive to keep these management tasks to a minimum.

  • Clarity with regard to evolution timeline: Multiple parallel versions of an API might be created—for example, when using the TWO IN PRODUCTION pattern. In such cases, it is necessary to keep careful track of the evolution of each API. One version might carry bug fixes only while another contains restructured messages that break the compatibility of the API. The API publication date is not meaningful in such cases because successor versions are released at different times, thereby making the date information meaningless with respect to guarantees to either client or provider.

When marking a new version of an API—regardless of whether explicit VERSION IDENTIFIERS are added to messages or versions are indicated elsewhere—the easiest solution is to use simple, single numbers as versions (version 1, version 2, and so on). However, this versioning scheme does not indicate which versions are compatible with each other (for example, version 1 might be compatible with version 3, but version 2 is a new development branch and will be further developed in versions 4 and 5). Thus, branching APIs—for example in a TWO IN PRODUCTION case—with a plain, single-number versioning scheme is hard because an invisible compatibility graph and several API branches have to be followed. This is because a single version represents the chronological order of releases but does not address any other concerns.

Another option is to use the commit ID of the API revision as the VERSION IDENTIFIER (depending on the source control system, this ID might not be numeric, as, for example, in Git). While this frees API designers and developers from having to assign version numbers manually, not every commit ID is deployed, and no indication of branches and compatibility might be visible to the API client.

How It Works

Introduce a hierarchical three-number versioning scheme x.y.z, which allows API providers to denote different levels of changes in a compound identifier. The three numbers are usually called major, minor, and patch versions.

Figure 8.2 illustrates a common numbering scheme.

Images

Figure 8.2 SEMANTIC VERSIONING. Version numbers indicate whether or not a change is compatible

The common numbering scheme in SEMANTIC VERSIONING works with three numbers:

  1. Major version. This number is incremented for incompatible, breaking changes, such as removing an existing operation. For example, a breaking change to version 1.3.1 will result in a new version 2.0.0.

  2. Minor version. This number is incremented if a new version provides new functionality (such as a new operation to an API or a new optional data element to the messages of an existing operation) in a compatible manner. For example, a compatible extension to version 1.2.5 will result in a new version 1.3.0.

  3. Patch version (also called fix version). This number is incremented for compatible bug fixes, such as changing and clarifying the documentation in an API contract or changing the API implementation to fix a logic error. For example, a compatible bug fix to version 1.2.4 will result in a new version 1.2.5.

SEMANTIC VERSIONING only describes how VERSION IDENTIFIERS are constructed, not how these identifiers are placed and used. This remark applies both to the versioned object (for example, whole API, individual endpoints and operations, and message data types) and to the places where identifiers are visible (for example, namespace, attribute contents, and attribute names). SEMANTIC VERSIONING can be applied both to versions that are not communicated to clients and to those that are.

Note the difference between API versions (visible to clients) and API revisions (chosen and handled internally by providers) that James Higginbotham explains in “A Guide for When (and How) to Version Your API” [Higginbotham 2017b]. Chapter 14 in Principles of Web API Design [Higginbotham 2021] covers this topic in depth.

Example

A startup wants to establish itself as a stock exchange data provider in the market. Its first API version (version 1.0.0) offers a search operation, which searches for a substring of the stock symbol and returns the list of matching stocks, including their full names and their prices in USD. Upon customer feedback, the startup decides to offer a historic search function. The existing search operation is extended to optionally accept a time range to provide access to historical price records. If no time range is supplied, the existing search logic is executed and the last known quote is returned. This version is fully backward compatible with the old version; old clients can call this operation and interpret its results. Thus, this version is called version 1.1.0.

A bug is discovered in the search function of version 1.1.0: not all stocks containing a supplied search string are found—only those that start with the string are returned. The API contract is correct but not fully implemented and not sufficiently tested. The API implementation is fixed and rolled out as version 1.1.1.

International customers are attracted to the services offered by the startup and request the support of international stock exchanges. As such, the response is extended to include a mandatory currency element. This change is incompatible from a client point of view, so the new version is numbered 2.0.0.

Note that this example is technology-independent on purpose. The supplied data can be transferred in any format, for example, as JSON or XML objects, and operations can be implemented using any integration technology (HTTP, gRPC, etc.). This pattern deals with the conceptual problem of issuing version identifiers based on the type of change introduced to the API interface and/or its implementation.

Discussion

SEMANTIC VERSIONING offers high clarity with regard to expressing the impact on compatibility of changes between two API versions. However, it increases the effort to assign accurate VERSION IDENTIFIERS because sometimes it is hard to decide to what category a change belongs. Such discussions about compatibility are especially difficult, but they provide important insights into the changes being made. If, however, the pattern is not applied consistently, breaking changes might sneak into minor updates; such violations should be watched for and discussed in daily standups, code reviews, and so on.

The clear separation of breaking and nonbreaking changes is achieved by the semantics of major versus minor/patch version numbers. This separation allows both API clients and providers to better assess the impact of changes, and thus the application of SEMANTIC VERSIONING increases the transparency of changes.

Additional aspects include manageability of API versions and related governance effort. The pattern lays the foundation for resolving this rather broad and cross-cutting force. By offering a means to clearly signal the extent of compatibility, additional patterns and measures can be applied.

A simplified version of the pattern uses only two numbers, n.m. For instance, Higginbotham suggests using a simplified major.minor semantic versioning scheme [Higginbotham 2017a]. Another option is to use three numbers but hide the third one from clients and use it only internally—perhaps as an internal revision number. Not disclosing patch versions avoids accidental coupling with clients that interpret the third version number received in messages—a number they were not meant to receive and use.

Both the API (and its contract) and the API implementation can and probably will be versioned one way or the other. Care must be taken and differences be communicated clearly because the version numbers of an interface and its implementation(s) usually will not match. When official standards evolve slowly, the version of the API and the versions of its implementations often differ. For example, a clinic management system might implement version 3 of the HL7 standard [International 2022] in its system software versions 6.0, 6.1, and 7.0.1

APIs that support replays of messages, for instance, those provided by distributed transaction logs such as Apache Kafka, require special attention during versioning. Distributed log transactions must be backward compatible should the client choose to replay the message history. Incompatible versions of a message would break the client. Hence, all message versions must be constantly in sync to support processing future and historical messages. The same holds for backup-and-restore capabilities in microservices-based systems (assuming that older, incompatible versions of data are restored and then re-exposed in the API) [Pardon 2018].

Related Patterns

SEMANTIC VERSIONING requires a VERSION IDENTIFIER. The three-digit VERSION IDENTIFIER may travel as a single string (with formatting constraints) or as an ATOMIC PARAMETER LIST with three entries. API DESCRIPTION and/or SERVICE LEVEL AGREEMENT can carry the part of the versioning information that matters to clients.

1. HL7 defines how systems exchange medical data.

The introduction of a RATE LIMIT often is an example of breaking change, requiring response messages to carry new ERROR REPORTS indicating that a limit has been passed.

All life-cycle patterns that differ in the level of commitment given by the API provider are related: LIMITED LIFETIME GUARANTEE, TWO IN PRODUCTION, AGGRESSIVE OBSOLESCENCE, and EXPERIMENTAL PREVIEW. When applying these patterns, SEMANTIC VERSIONING helps distinguish past, present, and future versions, calling out the compatibility guarantees and how they change.

To improve compatibility between versions, especially minor ones, the “Tolerant Reader” pattern can be used [Daigneau 2011].

More Information

More information on implementing SEMANTIC VERSIONING can be found online at Semantic Versioning 2.0.0 [Preston-Werner 2021].

For additional information on how to use semantic versioning in REST, the REST CookBook Web site [Thijssen 2017] includes a chapter on versioning. The API Style-book Web site also covers governance and versioning [Lauret 2017].

The Apache Avro specification [Apache 2021a] distinguishes the writer’s schema from the reader’s schema and identifies the cases in which these schemas match and do not match. The latter cases indicate incompatibility and/or interoperability issues, requiring a new major version.

Alexander Dean and Frederick Blundun introduce a structure and semantics for schema versioning [Dean 2014]. It utilizes the three version numbers of this pattern, specifically defining their meanings in the context of data structures. The first number is called model and is changed if all data readers break. The second is revision, which is incremented if some data readers break. The third is called addition and is incremented if all changes are backward compatible.

LinkedIn defines breaking and nonbreaking changes in its “API Breaking Change Policy” [Microsoft 2021].

Life-Cycle Management Guarantees

This section presents four patterns that explain when and how to publish and decommission API versions: EXPERIMENTAL PREVIEW, AGGRESSIVE OBSOLESCENCE, LIMITED LIFETIME GUARANTEE, and TWO IN PRODUCTION.

Images Pattern: EXPERIMENTAL PREVIEW

When and Why to Apply

A provider is developing a new API or a new API version that differs significantly from the published version(s) and is still under intensive development. As a result, the provider wants to be freely able to make any necessary modifications. However, the provider also wants to offer its clients early access so that these clients can start integrating against the emerging API and comment on the new API features.

How can providers make the introduction of a new API, or new API version, less risky for their clients and obtain early adopter feedback without having to freeze the API design prematurely?

  • Innovations and new features: Early access to emerging features raises customer awareness of a new API (or version), and gives customers time to decide whether to use the new API and initiate development projects. An iterative and incremental, or even agile, integration development process is supported; agile practices recommend releasing early and often.

  • Feedback: Providers desire feedback from early clients and/or key accounts to make sure they expose the right features with adequate qualities in their APIs. Many customers want to influence the API design by providing comments and suggestions about the developer experience.

  • Focus efforts: Providers do not want to document, manage, and support API prototypes with the same level of rigor as official versions. This helps focus their efforts and saves resources.

  • Early learning: Consumers want to learn about new APIs or API versions early so that they can plan ahead and build innovative products that leverage the new features.

  • Stability: Consumers appreciate stable APIs in order to minimize change efforts caused by frequent updates that they cannot benefit from (yet).

The provider could just release a new full-fledged API version when the development is finished. However, this means that clients cannot start developing and testing against the API until the release date. Developing the first client implementations might take several months; during this time, the API cannot be used, which results in revenue losses (for commercial API providers).

One way to counter these problems is to release API versions frequently. While this practice allows the client to sneak a peek at an API, the provider has to manage many versions. The provider will probably release many incompatible changes, which increases the governance effort further and makes it difficult for clients to closely track the latest API version.

How It Works

Provide access to an API on a best-effort basis without making any commitments about the functionality offered, stability, and longevity. Clearly and explicitly articulate this lack of API maturity to manage client expectations.

Figure 8.3 illustrates the pattern.

Images

Figure 8.3 Changes in EXPERIMENTAL PREVIEW sandbox and production

By releasing an unstable version as an EXPERIMENTAL PREVIEW in an ungoverned development sandbox, the provider makes an API version available to clients outside of the normal management process. For instance, the preview might not be governed by a SERVICE LEVEL AGREEMENT but still be documented by a draft API DESCRIPTION. Consumers volunteer to test and experiment with the new API version knowing that they cannot rely on its availability, stability, or any other quality level. By definition, the EXPERIMENTAL PREVIEW API might disappear suddenly or be available for a short and fixed amount of time only. Having early access to the API preview is especially beneficial for clients who have to estimate the effort required to integrate with the final version or who would like to jumpstart their own development while the API development is ongoing.

The EXPERIMENTAL PREVIEW, which covers the prerelease guarantees, often is complemented by an application of TWO IN PRODUCTION for governing the life cycle of production APIs. The EXPERIMENTAL PREVIEW can be made available to all known or unknown clients; alternatively, a closed user group can be selected for it (to limit support and communication effort).

Example

Let us assume that a fictitious software tools company wants to create a new product that lets it leave its comfort zone because it goes beyond the functionality offered in existing products. The company has been active in the development of a continuous build and deployment solution, currently offered as a cloud software service with a Web-based online user interface. Development teams at customers of the software tools company use the service to build their software by fetching a revision from a repository and deploying the built artifacts to configurable servers. Large customers have now requested an API to better trigger and manage builds and receive notifications about build states besides the Web interface. Because the software tools company has not yet offered any APIs for its products and thus lacks related knowledge and experience, the developers choose an EXPERIMENTAL PREVIEW of the API and improve it continuously by incorporating feedback from the customers who decide to adopt it early.

Discussion

EXPERIMENTAL PREVIEWS grant clients early access to API innovations and receive the opportunity to influence the API design. This is faithful to agile values and principles such as welcoming change and responding to it continuously. Providers have the flexibility to change the API freely and rapidly before declaring it stable. Learning and helping the provider to try out a new API and its features is different from writing production applications. Providers can introduce a grace period to ease the transition from preview to the production version. Early adopters perform a sort of acceptance testing, as they might find inconsistencies and missing functionality in this API version, resulting in changes without requiring the provider to follow a full-blown governance process.

As a downside, providers may find it difficult to attract clients due to a lack of long-term commitment to the experimental API, being perceived as immature. Clients have to keep changing their implementation until a stable version is released. Clients might face a total loss of investment if a stable API is never released and/or the preview disappears suddenly.

By offering an API in a nonproduction environment that is closely linked to the current development version, providers can offer peek previews into a new API or API version to interested clients. For this environment, different—and usually very lax—service levels (for example, regarding availability) are guaranteed. Consumers can intentionally decide to use this relatively unstable environment for giving feedback on the new API design and its functionality and to start development. However, clients can also choose to wait for the new production version of the API or stick to the current officially supported version (that is still provided with the standard service levels and thus is usually more stable and reliable).

When applied at the right time and with the right scope, EXPERIMENTAL PREVIEW allows and/or deepens the collaboration between providers and their clients and enables clients to more quickly roll out software that utilizes new API functionality. However, the provider organization must operate an additional runtime environment, for instance, providing different API endpoints in the same or another physical or virtual hosting location; the additional access channel might add systems management effort and has to be secured properly. It also makes its development progress on new APIs more transparent. This includes changes (and mistakes) that are not part of the final API, which become visible to external parties.

Related Patterns

EXPERIMENTAL PREVIEW is similar to traditional beta (testing) programs. This is the weakest support commitment given by an API provider (followed by AGGRESSIVE OBSOLESCENCE). When transitioning the API to a productive environment, another life-cycle governance pattern must be chosen, for instance, TWO IN PRODUCTION and/or LIMITED LIFETIME GUARANTEE. When the N in Production variant of TWO IN PRODUCTION is applied, an EXPERIMENTAL PREVIEW can be combined with any of these patterns.

The EXPERIMENTAL PREVIEW may have a VERSION IDENTIFIER but does not have to. An API DESCRIPTION should clearly state which version is experimentally previewed and which one is productive. Specific API KEYS can be assigned to grant certain clients access to the preview/the beta version.

More Information

Vimal Maheedharan shares tips and tricks on beta testing in his article “Beta Testing of Your Product: 6 Practical Steps to Follow” [Maheedharan 2018].

James Higginbotham advises keeping supported and unsupported operations separate and getting feedback early and often. He recommends the following stability states of API operations: experimental, prerelease, supported, deprecated, and retired [Higginbotham 2020].

Images Pattern: AGGRESSIVE OBSOLESCENCE

When and Why to Apply

Once an API has been released, it evolves, and new versions with added, removed, or changed functionality are offered. In order to reduce effort, API providers no longer want to support certain functionalities for clients, for instance, because they are no longer used regularly or have been superseded by alternative versions.

How can API providers reduce the effort for maintaining an entire API or its parts (such as endpoints, operations, or message representations) with guaranteed service quality levels?

  • Minimizing the maintenance effort: Allowing the provider to discontinue support of rarely used parts of an API or an API as a whole helps reduce the maintenance effort. Supporting old clients can be particularly painful; for instance, the required skills and experience (regarding notations, tools, and platforms in certain versions) might differ from those required to evolve the current version.

  • Reducing forced changes to clients in a given time span as a consequence of API changes: It is often not possible to just switch off an old version. Clients usually do not follow the same life cycle as their providers: even within the same organization, it is often difficult or impossible to roll out upgrades to two systems at the same point in time, for instance, if different teams own these systems. The problem becomes worse if different organizations own the systems; in this case, the API provider might not even know the client developers. Therefore, it is often necessary to decouple the life cycles of clients and providers. This decoupling can be achieved by giving clients time to make any necessary changes. To reduce the impact of changes to clients, it is also useful to be able to remove only certain obsolete parts of an API (for instance, operations or message elements in requests and responses), not the entire API version. Removing only parts of an API reduces the impact of API changes to clients compared to withdrawing support of an entire API version. The removal might not affect those clients that do not depend on the particular discontinued feature(s).

  • Respecting/acknowledging power dynamics: Organizational units and teams may influence each other in various ways, from formal or informal hearings to official voting and approvals. Political factors influence the design decisions. For example, high-profile customers often have good leverage with competing providers that can be swapped in and out easily because their APIs are similar or identical. In contrast, a monopolist offering a unique API can impose changes on millions of clients without involving them much—as they have nowhere else to turn. Depending on the ratio of API clients per provider, it might be worthwhile to shift more implementation effort to one or the other.

  • Commercial goals and constraints: Removing obsolete APIs or API features might have monetary consequences if a commercial PRICING PLAN is in place. There is a risk that the API product becomes less valuable if features are cut but the price remains the same (or increases). Providers might try to motivate clients to move over to another offering, such as a new product line, to reducing maintenance costs for the old product. To do so, clients can be asked to pay extra fees for certain old features or be offered discounts for the new ones.

One could give no guarantees or assure a rather short LIMITED LIFETIME GUARANTEE, but such weak commitments do not always minimize the impact of changes as desired. One could declare an API to be an EXPERIMENTAL PREVIEW, but this is an even weaker commitment that clients might not receive well.

How It Works

Announce a decommissioning date to be set as early as possible for the entire API or its obsolete parts. Declare the obsolete API parts to be still available but no longer recommended to be used so that clients have just enough time to upgrade to a newer or alternative version before the API parts they depend on disappear. Remove the deprecated API parts and the support for it as soon as the deadline has passed.

AGGRESSIVE OBSOLESCENCE makes old API versions as a whole—or parts thereof—unavailable rather quickly, for instance, within a year (or even less) for an enterprise application API.

When releasing an API, the provider should clearly communicate that it follows such an AGGRESSIVE OBSOLESCENCE strategy, which means that a particular feature might be deprecated and subsequently decommissioned anytime in the future (removed from support and maintenance, that is). When an API, operation or representation element is to be removed, the provider declares this element of the API as deprecated and specifies a point in time when the feature will be removed completely. Depending on their market position and the availability of alternatives, clients can then choose to upgrade or switch to a different provider.

When a provider releases an API, it reserves the right to deprecate and then remove parts of it later. The parts may be entire endpoints, operations exposing certain functionality, or specific representation elements in the request and response messages (such as a particular in or out parameter). Hence, planning obsolescence and removal involves three steps. Figure 8.4 illustrates them.

Images

Figure 8.4 Stepwise approach to AGGRESSIVE OBSOLESCENCE: The API provider first releases V1 of API functionality. While this version is available, the provider deprecates and then removes it

The three steps are as follows:

  1. Release. An API version is used in production (V1 in the figure). Clients happily use it.

  2. Deprecate. The provider announces the deprecation of an API or some of its parts within an API version and indicates when these parts will be removed (for example, when the next version of the API is published, V2 in the figure). Clients receive the announcement and can start the migration to the new version—or, in extreme cases, switch to alternative providers.

  3. Remove/decommission. The provider deploys a new API version that no longer supports the deprecated parts (V2 in the figure). The old version is taken down; requests to the old endpoint either fail or are redirected to the new version. When the removal/decommissioning takes place, clients who depend on any removed parts of the API (because they did not migrate to a newer version) will no longer have access to those parts.

The AGGRESSIVE OBSOLESCENCE strategy is eligible when the needs of the provider outweigh those of the clients. By clearly announcing the deprecation and removal schedule for old versions of APIs or API parts, the provider can reduce and limit effort for supporting API parts that are not support-worthy in a broad sense, such as economically, because a feature is too costly to maintain (for example, rarely used features), or legally, because some functionality becomes unavailable. For instance, the introduction of the International Bank Account Number (IBAN) to identify bank accounts replaced old account numbers, and the introduction of the euro currency replaced many other currencies; APIs dealing with accounts and currencies had to be changed accordingly.

The notification of a deprecation and later decommissioning date allows clients to plan the required effort and schedules for continuing to use the old API while they migrate to an alternative way of achieving the required functionality. Communicating which entities are deprecated and when they will be removed may require adding special “sunset” markers, for instance, in protocol headers or METADATA ELEMENTS. An alternative, simple solution is sending client developers an email reminding and warning them that they are still depending on API features that will disappear soon.

Sometimes AGGRESSIVE OBSOLESCENCE might be the only option for API providers that have not yet declared any life-cycle policies. If no guarantees are given, deprecating features and announcing—possibly generous—transition periods might be a proper way to be able to introduce incompatible changes.

Example

A payment provider offers an API that allows clients to instruct payments from their accounts to other accounts. Accounts can be identified by old-fashioned, country-specific account and bank numbers or by IBANs.2 Because IBANs are the new standard and the old account and bank numbers are rarely used, the API provider decides not to support the old numbers anymore. This allows the provider to delete parts of its implementation, thereby reducing maintenance effort.

In order to allow old clients to migrate to the IBAN scheme, the provider publishes a removal announcement on its API documentation Web page, marks the account and bank number as deprecated in the API documentation, and notifies its registered clients. The announcement states that the old, country-specific functionality will be removed after one year.

After one year, the payment provider deploys a new implementation of the API that has no support for the old account and bank numbers and removes the old, country-specific attributes from its API documentation. Calls using the old and removed functionality will fail from now on.3

2. IBANs originally were developed in Europe but are now used in other parts of the world as well. They have become an ISO standard [ISO 2020].

Discussion

This pattern allows a rather fine-grained way to change APIs: in the best case, clients do not have to change at all if functionality that becomes obsolete is not used, while the provider code base is kept small and thus simple to maintain. The pattern can be applied not only proactively but also reactively during API maintenance.

Providers must announce which features are deprecated and when these features will be decommissioned. However, clients that rely on rarely used features or take full advantage of all API features are forced to change by implementing modifications within a schedule that might have been unknown when the decision to use a particular API was made. The decommissioning time is communicated to the clients upon deprecation and not during the API release (in contrast to the LIMITED LIFETIME PATTERN); therefore, it might or might not fit the client’s release roadmap. Furthermore, deprecation times and decommissioning periods might differ per API part. Another challenge is that clients must be notified about obsolete parts, which might be challenging in some PUBLIC API scenarios. Rightsized, pragmatic API governance helps in this case.

AGGRESSIVE OBSOLESCENCE can be used to enforce a coherent and secure ecosystem around the APIs offered: for example, replacing weak cryptographic algorithms, out-phased standards, or inefficient libraries can help in achieving a better overall experience for all involved parties.

The AGGRESSIVE OBSOLESCENCE pattern emphasizes the reduction of effort on the provider side but burdens clients. Essentially, it requires the clients to evolve continuously with the API. In turn, clients stay current with the newest functions and improvements and thus benefit from switching away from old versions; for instance, they are forced to use new or updated (improved) security procedures. Depending on the deprecation period, clients can plan and follow API changes but are required to remain rather active.

Depending on the API and API endpoint types and their versioning policies, explained in the VERSION IDENTIFIER and SEMANTIC VERSIONING patterns, it is not straightforward to come up with a suited deprecation and decommissioning approach. Master data, for instance, might be harder to be removed from message representations than operational data. It takes effort to maintain a precise list of deprecated parts in the API DESCRIPTION, and it is important to plan when these parts will eventually be removed from the API.

3. Note that in this case, legislature also had specified a transition period to the IBAN system, in effect deprecating the old, country-specific account number scheme.

In in-house scenarios in particular, the knowledge of which systems are currently using an API (or the deprecated subset of an API) is of great help when deciding if—and which—features or APIs should be removed. Intercompany services are usually more restrictive and are designed to ensure that other systems continue to work properly. Thus, additional care must be taken before an API or functionality is finally removed. Knowing the relationships between systems and establishing dependency traceability can help with this problem in both scenarios. DevOps practices and supporting tools can be leveraged for such tasks (for instance, for monitoring and distributed log analysis). Enterprise architecture management may provide insights regarding active and stale system relationships.

In some business contexts, external clients are not supported with great care because their API usage is not very important to the API provider (for instance, commodity services that validate data or convert it from one notation or language to another). In such circumstances, using the PRICING PLAN pattern (or at least some metering mechanisms) can help identify services to be deprecated and eventually removed. PRICING PLANS can help to financially measure the economic value of an API that can be compared with the maintenance and development effort, thereby deriving an economic decision about prolonging the API lifetime.

Related Patterns

Several strategies for discontinuation of API parts can be employed, as portrayed in the TWO IN PRODUCTION and LIMITED LIFETIME GUARANTEE patterns. The AGGRESSIVE OBSOLESCENCE pattern can be used in a fine-grained way. While other strategies are attached to entire APIs, endpoints, or operations, only certain representation elements might get deprecated and removed in AGGRESSIVE OBSOLESCENCE, thereby allowing less obstructive changes.

Another difference to other patterns is that AGGRESSIVE OBSOLESCENCE always uses relative timeframes for removing functionality: because functionality becomes obsolete during the lifetime of an API, it is flagged as deprecated within its active period, and the deprecation period runs from this time onward. In contrast, TWO IN PRODUCTION or LIMITED LIFETIME GUARANTEE can be used with absolute timeframes based on the initial release date.

AGGRESSIVE OBSOLESCENCE may or may not use a VERSION IDENTIFIER. If present, an API DESCRIPTION or a SERVICE LEVEL AGREEMENT should indicate its use.

More Information

Managed Evolution [Murer 2010] shares general information on service governance and versioning, for instance, how to define quality gates and how to monitor traffic. Chapter 7 discusses ways to measure the managed evolution.

Planned obsolescence is discussed in “Microservices in Practice, Part 1” [Pautasso 2017a]. Here, the plan foresees a rather short lifetime.

Images Pattern: LIMITED LIFETIME GUARANTEE

When and Why to Apply

An API has been published and made available to at least one client. The API provider cannot manage or influence the evolution roadmaps of its clients, or it considers the financial or reputation damage caused by forcing clients to change their implementation to be high. Therefore, the provider does not want to make any breaking changes in the published API but still wants to improve the API in the future.

How can a provider let clients know for how long they can rely on the published version of an API?

  • Make client-side changes caused by API changes plannable: When clients have to modify their code because an API changes in an incompatible manner, the modification should be plannable well ahead of the publication of the new API version. This allows clients to align their development roadmap and allocate resources in their project planning, thereby reducing late migration problems. Some clients cannot (or do not want to) migrate to newer API versions—at least for a considerable time span.

  • Limit the maintenance effort for supporting old clients: Providers strive for low costs for development and operations. Refactoring an API may make it easier to use and lower the development and maintenance effort [Stocker 2021a]. However, other factors increase the effort for the provider. These factors include the support of older or less used API parts.

How It Works

As an API provider, guarantee to not break the published API for a fixed time-frame. Label each API version with an expiration date.

Figure 8.5 sketches the timeline resulting from such a LIMITED LIFETIME GUARANTEE.

Images

Figure 8.5 API life cycle when using LIMITED LIFETIME GUARANTEE. Removal time is stated at publication time

The provider promises to keep the API usable for a defined, limited but considerably long time and retires it after that. This practice keeps the client safe from unwanted negative impact or outages. It also sets a fixed expiration deadline that the client can plan for each time a version is published.

The advantage of a fixed time window serving as LIMITED LIFETIME GUARANTEE (instead of fixing the number of active versions, which is the approach taken in the TWO IN PRODUCTION pattern) is that no further coordination between the provider and client organization is necessary. When using an API version for the first time, the client already knows when it has to adapt and release a version of its application that is compatible with the then current API version.

The LIMITED LIFETIME GUARANTEE pattern stresses the stability for the client side via a built-in expiration time; outdated versions can be decommissioned right away when the time comes. The provider guarantees that the API will never change in an incompatible manner in the preannounced time span and agrees to implement any reasonable measure to keep the API up and running in a backward-compatible manner during that time.

In practice, guaranteed timeframes often are multitudes of 6 months (for instance, 6, 12, 18, or 24 months), which seems to provide a good balance for provider and client needs.

Example

One example for a LIMITED LIFETIME GUARANTEE was the introduction of the IBAN in Europe. The limited lifetime was specified in a 2012 resolution of the European Parliament [EU 2012] granting a period until 2014 after which the old, national account numbers had to be replaced by the new standard; the use of IBANs became compulsory after that. This regulatory requirement naturally had an impact on software systems that have to identify accounts. The services offered by such systems had to issue a LIMITED LIFETIME GUARANTEE for the old API operations, which used the old account numbers. This example shows that versioning and evolution strategies not only are decided by the API provider alone but can also be influenced or even mandated by external forces (such as legislation or industry consortia).

Discussion

In general, this pattern allows planning well ahead due to fixed time windows that are known in advance. It does so by limiting the provider’s ability to respond to urgent change requests if they impact compatibility.

Customers are forced to upgrade their API clients at a well-defined, fixed point in time that might conflict with their own roadmap and life cycle. This can be a problem if API clients that are still in use are no longer actively maintained. Changing existing clients might not even be possible, for instance, if a software vendor no longer actively maintains its products.

This pattern is applicable if the provider can constrain the API evolution to include only backward-compatible changes during the fixed lifetime guarantee. Over time, effort to do so will increase, and the API will build up technical debt by introducing changes in a backward-compatible way that the client can still interpret. This debt increases the effort on the provider side, for instance, for regression testing and maintaining the API; the provider has to live with this added debt until it is permitted to change or revoke the API.

While the LIMITED LIFETIME GUARANTEE is usually part of the SERVICE LEVEL AGREEMENT between provider and client, it has large implications on the provider. The longer the guarantee is valid, the higher the burden is on the provider development organization. To keep the published API stable, the provider will usually first try to make all changes in a backward-compatible manner. This might lead to unclean interfaces with awkward names put in place in order to support both older and newer clients. If changes cannot be (efficiently) made to the existing version, a new API version might be developed. This new version must run in parallel with the old version in order to fulfill the guarantee.

Furthermore, the API freeze caused by the guarantee might inhibit progress and integration of new technologies and features on the provider side that in turn may also hinder clients.

In some settings, providers may want to get rid of clients who do not upgrade when the lifetime guarantee expires. For example, due to mistakes in the API design or progress in the area of cryptography, security risks to the whole ecosystem of the provider and all clients might arise. Introducing the LIMITED LIFETIME GUARANTEE pattern offers an institutionalized way of enforcing timely client updates.

Related Patterns

More lenient approaches give the provider more freedom with respect to releasing incompatible updates; they are presented in the AGGRESSIVE OBSOLESCENCE and TWO IN PRODUCTION patterns. The LIMITED LIFETIME GUARANTEE pattern shares some properties with AGGRESSIVE OBSOLESCENCE. In both cases, the API must not change in an incompatible way within the announced time span. The fixed time span here in LIMITED LIFETIME GUARANTEE implies an implicit deprecation notification; the end of the guarantee is the decommissioning time. After the guaranteed time span has expired, the provider may make any changes, including breaking ones, or may discontinue the expired API version altogether.

A LIMITED LIFETIME GUARANTEE usually has an explicit VERSION IDENTIFIER. The API DESCRIPTION and, if present, a SERVICE LEVEL AGREEMENT should indicate the actual expiration date for the API version in order to inform API clients of the upcoming need to take action and upgrade.

More Information

Managed Evolution [Murer 2010] gives rich advice on service versioning and service management processes, for instance, including quality gates. Section 3.6 mentions service retirement.

Images Pattern: TWO IN PRODUCTION

When and Why to Apply

An API evolves and new versions with improved functionality are offered regularly. At some point in time, the changes to a new version are no longer backward compatible, thereby breaking existing clients. However, API providers and their clients, especially those of a PUBLIC API or a COMMUNITY API, evolve at different speeds; some of them cannot be forced to upgrade to the latest version in a short timeframe.

  • Allow the provider and the client to follow different life cycles: When changing an API over time, one of the main problems is how (and for how long) to support clients of older API versions. Keeping old API versions alive usually requires additional resources for operations and maintenance—for instance, bug fixes, security patches, upgrades of external dependencies, and subsequent regression testing cause work required for each version. This inflicts cost and binds developer resources.

    It is not always possible to simply switch off an old version, because the life cycles and evolution of API clients and their providers often differ. Even within the same company, it is difficult or even impossible to roll out multiple systems that depend on each other at the same point in time, especially if they are owned by different organizational units. The problem becomes worse if multiple clients are owned by different organizations or if the clients are unknown to the provider (for instance, in PUBLIC API scenarios). Thus, it is often necessary to decouple the life cycles of the client and the provider. Enabling such autonomous life cycles is one of the core tenets of microservices [Pautasso 2017a].

    Under independent life cycles with different API publication frequencies and release dates of API provider implementations and their clients, it becomes necessary to plan API evolution from the very beginning of API design and development because it is impossible to make arbitrary changes to an already published API.

  • Guarantee that API changes do not lead to undetected backward-compatibility problems between clients and the provider: Introducing only changes that are backward compatible is hard, particularly if done without tools that automatically check for incompatibilities. There is a risk that changes will quietly introduce problems—for instance, when changing the meaning of existing elements in requests and responses without making the changes visible in the message syntax. An example is the decision to include the value added tax in a price, without changing parameter name or type. Such semantical change cannot be detected by message receivers easily, even in API tests.

  • Ensure the ability to roll back if a new API version is designed badly: When redesigning or restructuring an API thoroughly, the new design might not work as expected. For example, functionality still required by some clients might unintentionally be removed. Being able to fall back and undo a change helps to avoid breaking those clients for a while.

  • Minimize changes to the client: Clients in general appreciate API stability. When an API is released, it can be assumed to work as intended. Updates bind resources and cost money (which would be better spent on delivering more business value). However, providing a highly stable API requires upfront effort on the provider side; provider-side agility may trigger frequent client-side changes that might come unexpectedly and might not always be welcome.

  • Minimize the maintenance effort for supporting clients relying on old API versions: Any life-cycle management strategy not only must take the client effort into consideration but also must balance it with the effort on the provider side to maintain multiple API versions, including versions supporting infrequently used (and therefore unprofitable) features.

How can a provider gradually update an API without breaking existing clients but also without having to maintain a large number of API versions in production?

How It Works

Deploy and support two versions of an API endpoint and its operations that provide variations of the same functionality. These TWO IN PRODUCTION do not have to be compatible with each other. Update and decommission the versions in a rolling, overlapping fashion.

Such a rolling, overlapping support strategy can be realized in the following way:

  • Choose how to identify a version, for instance, by using the VERSION IDENTIFIER pattern.

  • Offer a fixed number (usually two, as indicated in the pattern name) of API versions in parallel and inform your clients about this life-cycle choice.

  • When releasing a new API version, retire the oldest one that still runs in production (which is the second last one by default) and inform remaining clients (if any) about their migration options. Continue to support the previous version.

  • Redirect calls to the retired version, for instance, by leveraging protocol-level capabilities such as those in HTTP.

By following these steps, a sliding window of active versions is created (see Figure 8.6). Thereby, providers allow clients to choose the time of migration to a newer version. If a new version is released, the client can continue to use the previous version and migrate later. They can learn about the API changes and required client-side modifications without risking the stability of their own primary production system.

Variant Although typically two versions are offered in parallel, this pattern can also be applied in a slightly changed variant: In N in Production more than two versions are supported.

In N in Production, the sliding window of active versions is increased to N (with N greater than 2). This strategy gives clients more time and more options to upgrade but places more maintenance effort and operational cost on the provider side, obviously.

Example

A business software vendor releases version 1 of a payroll API for its enterprise resource planning (ERP) system. In the continued development of this ERP system, the payroll API is extended with new pension plan management features. At some point, the new features break the API because their data retention policies are incompatible with the previously used ones; a new major version, version 2, is published. Because the vendor supports TWO IN PRODUCTION, it releases its software with the old API (version 1) and the new API with pension plan management features (version 2). Customers who use version 1 can update the ERP system and then start migrating to version 2. New customers who need the pension plan management feature can start to use the API version 2 right away.

With the next release of the ERP system, the software vendor again publishes a new API (version 3) and removes support for version 1; versions 2 and 3 are now the TWO IN PRODUCTION. Customers that still use version 1 are cut off until they have migrated to version 2 or 3 (that they can be redirected to). Clients using version 2 can continue to use it until version 4 comes out. Version 5 brings end of life for version 3, and so on.

Images

Figure 8.6 Version lifetimes when using TWO IN PRODUCTION. Clients always have a choice between two versions

Discussion

TWO IN PRODUCTION disentangles the life cycle of provider and client. API clients do not have to release their software every time the provider does so in a breaking way. Instead, they are granted a time window in which they can migrate, test, and release their software updates. However, clients must move eventually, as they cannot rely on an eternal lifetime guarantee for the API. This means that they have to plan and allocate resources for upgrading their software over its lifetime as the two versions in production come and go.

The provider can use this pattern to make brave changes in a new API version because existing clients will stay on the old version until they migrate. This gives the provider more freedom to refine the API incrementally.

When TWO IN PRODUCTION is used, the effort of providers and clients are balanced: customers have a defined time window to migrate their API clients to a new API version, while API providers do not have to support an unlimited number of versions for an undefined, possibly inappropriately long amount of time. As a result, this pattern also clarifies the responsibilities of both parties to plan their life cycle: the provider can introduce new and possibly incompatible versions but must support multiple versions, whereas the client must migrate to a new version in a limited time but can plan its release schedule rather freely and flexibly.

For clients, however, it might be difficult to know when development activity is required: unlike in the LIMITED LIFETIME GUARANTEE pattern, the removal of API versions is dynamic and depends on other API releases. Therefore, it cannot be planned as easily (unless the patterns are combined).

Related Patterns

The use of this pattern usually requires the VERSION IDENTIFIER pattern in order to distinguish the API versions that are currently active and supported concurrently. Fully compatible versions, for instance, as indicated by the patch version in SEMANTIC VERSIONING, can replace active versions without violating the TWO IN PRODUCTION constraints; the TWO IN PRODUCTION are two major versions. This should be reported in the API DESCRIPTION and/or the SERVICE LEVEL AGREEMENT.

AGGRESSIVE OBSOLESCENCE can be applied to one of the TWO IN PRODUCTION to force clients to stop using the older API version and migrate to a newer one so that the provider can introduce an even newer API version. If the client requires more guarantees on the expiration date of the old API version, it might be better to combine TWO IN PRODUCTION with a LIMITED LIFETIME GUARANTEE.

An EXPERIMENTAL PREVIEW can be one of the two (or N) versions running in production when following this pattern.

More Information

Managed Evolution covers life-cycle management on a general level, but also dives into API versioning. Section 3.5.4 reports a combined usage of SEMANTIC VERSIONING and TWO IN PRODUCTION. Three versions are reported to have proven to be a good compromise between provider complexity and adaptation pace [Murer 2010].

“Challenges and Benefits of the Microservice Architectural Style,” a two-part article on the IBM Developer portal [Fachat 2019], recommends this pattern.

Summary

This chapter presented six patterns that are concerned with the evolution of APIs. Two patterns covered versioning and compatibility management: VERSION IDENTIFIER and SEMANTIC VERSIONING. It is much easier to detect the presence and the impact of changes if each API revision is properly identified. The VERSION IDENTIFIER should clearly indicate whether or not the new version is compatible with previous ones. Major, minor, and patch versions should be distinguished.

The remaining four patterns focused on the API life cycle by balancing the client desire for stability and the provider need to restrict maintenance effort. EXPERIMENTAL PREVIEW helps introduce changes and get feedback about them from interested clients without committing to their stability, as in an official release. TWO IN PRODUCTION eases the migration of clients by offering them two or more versions of the API at the same time. AGGRESSIVE OBSOLESCENCE and LIMITED LIFETIME GUARANTEE make it explicit that no API lasts forever and clients should be aware that someday their dependencies will cease to function (at least in part). Obsolescence can be declared at any time, with a grace period granted (called deprecation period); by definition, a lifetime guarantee is established at API publication time.

A rather extreme solution for an API provider would be an experimental preview in production, which could also be called “living at head” or “surfing the latest wave.” No guarantees for compatibility are given, and clients that intend to continue working over long periods must stay in sync with the most current API version. However, doing so requires effort and often is not a viable option.

In contrast to most other patterns in this book, only a few of the evolution patterns directly affect the syntax of request and response messages: a VERSION IDENTIFIER may be placed in the message and use an ATOMIC PARAMETER serving as a METADATA ELEMENT to transmit the version—whether it follows the SEMANTIC VERSIONING scheme or not.

The versioned subject can reside on different levels of abstraction: entire API, endpoints, individual operations, and/or data types used in the request and response messages. The same is true for the life-cycle guarantees offered in an AGGRESSIVE OBSOLESCENCE policy. Operations applying the REQUEST BUNDLE pattern are a special case, posing the question whether all requests packaged in the request container must have the same version. Mixing versions might be desirable but complicates provider-side request dispatching.

OPERATIONAL DATA HOLDERS that implement mission-critical, innovative features often are often exposed to test clients and early adopters in an EXPERIMENTAL PREVIEW; they might also realize AGGRESSIVE OBSOLESCENCE and be replaced with a newer API and API implementation frequently. MASTER DATA HOLDERS tend to give longer LIMITED LIFETIME GUARANTEES than other types of information holders. Their clients benefit from a TWO IN PRODUCTION policy particularly well. REFERENCE DATA HOLDERS might rarely evolve; if they do, TWO IN PRODUCTION is eligible in this scenario, too. A long-running PROCESSING RESOURCE with STATE TRANSITION OPERATIONS representing business activities might not only have to migrate API and API implementation (including database definitions) when upgrading the VERSION IDENTIFIER to a new major version but also have to upgrade all process instances.

Evolution strategies should be documented in the API DESCRIPTION and SERVICE LEVEL AGREEMENT of the API. RATE LIMITS and PRICING PLANS have to be changed as the API evolves; changes in these artifacts might also trigger version upgrades.

Service Design Patterns [Daigneau 2011] includes a chapter called “Evolution.” Two of its six patterns (Breaking Changes, Versioning) are not available online but are presented in the book. Tolerant Reader and Consumer-Driven Contracts deal with evolution; the remaining two patterns (Single Message Argument, Dataset Amendment) focus on message construction and representation, which have an impact on evolution. One particular life-cycle model is described in an IBM Redpiece covering an API management solution [Seriy 2016].

Chapter 13 of the RESTful Web Services Cookbook [Allamaraju 2010] is devoted to extensibility and versioning in the context of RESTful HTTP. It presents seven related recipes—for example, how to maintain URI compatibility and how to implement clients to support extensibility. Roy Fielding expresses his views on versioning, hypermedia, and REST in an InfoQ interview [Amundsen 2014]. James Higginbotham covers the topic in “When and How Do You Version Your API?” [Higginbotham 2017a]. The microservices movement suggests nontraditional approaches to life-cycle management and evolution; see “Microservices in Practice: Part 2” [Pautasso 2017b] for a discussion.

Next up is Chapter 9, “Document and Communicate API Contracts,” covering API contracts and descriptions, including both technical and business aspects.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.27.72