OpenAPI-Specification
OpenAPI-Specification copied to clipboard
Enhanced Operation Deprecation and versioning
Currently, OpenAPI allows us to identify the version of the API being described and deprecate operations using a Boolean value.
I would like to propose some additional metadata that makes it easier to track changes to APIs.
In addition to the existing deprecated flag, there is now a deprecatedVersion and replacementOperationId property.
Consider the following API and two of its operations:
{
"openapi" : "3.0.0",
"info" : {
"title" : "My API",
"version" : "1.0.313"
}
{
"/foo" : {
"get" : {
"operationId" : "getFoo",
"description" : "Return a foo",
"deprecated" : true,
"deprecatedVersion" : "1.0.312",
"replacementOperationId" : "getFoo2"
"responses" : {
"200" : {
"description" : "A foo representation"
}
}
},
"/foo2" : {
"get" : {
"operationId" : "getFoo2",
"description" : "Return a foo",
"responses" : {
"200" : {
"description" : "A completely different breaking foo representation"
}
}
}
}
}
}
The deprecatedVersion property indicates the API version in which this operation was deprecated. The replacementOperationId provides a pointer to a new operation that replaces the functionality of the deprecated operation.
These new properties enable a variety of useful capabilities:
- Display documentation that is relevant to a particular API version. Operations that were deprecated prior to that version can be hidden by default to prevent accidently usage of operations that have been replaced.
- Help developers migrate to new operations by allowing documentation of deprecated operations to point to replacement operations.
- Generate client code that only supports operations available in a particular API version.
- Makes it possible to manage APIs to that are changing incrementally instead of doing big bang updates that completely replace a prior API.
- Help developers identify what has changed in a new API release.
Hmm, maybe also a field indicating until when an API will be supported?
@ePaul It would be nice if we can provide that information. However, how reliable is it going to be? And should it be a date, or should it be a version when it will be removed?
Anyway, I think support for this might have to wait, as we have higher priority breaking changes that we want to get resolved ASAP. We can always experiment with x- properties for stuff like this.
I just wanted to get the idea on record before I forgot it :-)
We would like to also be able to mark an entire API as deprecated. Repeating some internal discussion:
The simplest change to support this would be to add the "deprecated" property at the top level (the "root document object"). If the property is true, the API is deprecated; if not, it's still active.
For API deprecation, some additional information might be useful:
- The date of the original deprecation.
- The date of final shutdown.
- Deprecation strategy:
- Are new users permitted or forbidden?
- Is there any SLO for this API?
- Is there any system degradation due to the deprecation (eg. higher latency, intermittent errors, lower quotas, warning messages in response headers, etc)? This could either be purely informative or this setting could directly affect how the backend behaves.
- Links to any replacement APIs.
We would like to also be able to mark an entire API as deprecated. Repeating some internal discussion:
Came here to upvote this point. The scenario is all too common that you want to phase out v1 of an API end of next year in favor of v2 being released today. We should be able to mark this in the v1 spec document so contract-first clients and API generators can issue a loud warning pointing to the correct and supported API version.
- In light of the change to the definition of
versionfrom #834,deprecatedVersionwould refer to the version of the OpenAPI document, not the version of the API itself. - How would you deal with APIs that combine or split, rather than just have a one-to-one replacement?
I have been dealing with a similar thing in something I'm working on.
To add some of my thoughts: Lots of APIs don't have a single version, and imposing one would be tough. This should work for an evolving API with a version like "4.0.342" as suggested, but it also needs to work for URL versioned/namespaced APIs (as much as we hate em).
This is why date and version are both important.
Replacements are also interesting, because whilst it would often be ideal to say "this field is gone, use this one", sometimes fields just go without a replacement, or split or change fundamentally.
Pointing callers to the replacement field or the replacement endpoint is one thing, but we gotta send people somewhere when that is not an option... URL to docs? /shrug
just for completeness: while OpenAPI is about design time, it might be interesting to also consider runtime issues. for deprecating complete APIs, the HTTP Sunset header should be an RFC any minute now (https://tools.ietf.org/html/draft-wilde-sunset-header-10), and that could nicely complement any description information with real-time runtime signals.
i would refine @dret's comment above. There are two relevant HTTP headers for deprecation: Deprecation (https://tools.ietf.org/html/draft-dalal-deprecation-header-00) and Sunset. To inform about the deprecation, the Deprecation HTTP header should be used. To inform about sunset of the deprecated resource, the Sunset header should be used in addition to the Deprecation header. This is described in section #5 https://tools.ietf.org/html/draft-dalal-deprecation-header-00#section-5.
Draft #11 https://tools.ietf.org/html/draft-wilde-sunset-header-11 of the Sunset header clarifies this aspect as well in section 1.4 https://tools.ietf.org/html/draft-wilde-sunset-header-11#section-1.4.
yes, completely agree with @sdatspun2 here: HTTP Sunset is only for sunsets, and deprecation should be done with HTTP Deprecation.
Yep, now that both Sunset and Deprecation exist, there is not much need for further work on this. With OpenAPI mostly being used for design time, simply being able to say "This is now deprecated" is enough, as runtime code will sniff for deprecation.
For example, your OpenAPI generated Ruby SDK can just reference faraday-sunset and you will have an error in your logs or bug reporting system if an endpoint is suddenly sunset. Sniffing for deprecations is equally possible. I think all of this is more powerful than trying to get super specific about what might go away when in design time. :)
Allowing a Link Object for or instead of replacementOperationId adds the power of expressions to describing the vLatest call
It's a long time since my previous comment and for some reason I now love the idea of "replacementOperationId" : "getFoo2" or similar.
I can already imagine it being used in the new Stoplight docs we're cooking up, it'd be amazing for helping folks power through API evolution.
Design time is often considered something that happens first, then you build, then you run it, but that progression is not linear. In the design-first workflow, design happens first as the name suggests, but also the entire way through the lifecycle, with every new endpoint or property being added to the design over time.
As such it's perfectly normal for runtime code to emit deprecation and sunset headers, those headers could even be powered by a middleware that looks for these deprecation flags in OpenAPI. Seeing as The Stoplight Way is to have your OpenAPI and code in the same repo, this would be incredibly easy to do, and stop the duplication of information between having to add the deprecation flag in the description and then also add it in the code... 🤔
What do folks think?
Hi all 👋
What about adding a sunset field?
The sunset field could indicate the next version where the deprecated item will be removed.
It could look like this:
openapi: 3.1.0
info:
version: 1.0.313
paths:
/foo:
get:
deprecated: true
sunset: 4.0.0
This allows:
- documenting the sunset
- implementing a lint rule (with Spectral or such) that triggers an error if we forget to remove something deprecated when the version is bumped. (e.g. the day we bump the spec above to
4.0.0, we would get an error if we don't remove theGET /foooperation - if the API or the API gateway are informed of the upcoming versions' release dates, we can easily automate the presence & value of the
Sunsetheader in the response.
By the way, in order to improve docs and make the 3rd item easier, what about adding the version history or the release planning in the info? Something like info: {history: [{version: 4.0.0, releasedAt: '2050-01-01'}]}. Probably out of scope but just putting here while I'm at it 😅
Should we keep this here, or should I open a distinct issue for the sunset field.
Could you help me with a use-case? What is the benefit of having sunset (a runtime HTTP header that talks about removal) in OpenAPI specifically?
You can already use deprecated: true to say "This is going away soon" and then the implementation is going to give you more specifics through the Deprecation and/or Sunset headers. The implementation may move the date back if not enough folks are doing it, and in that case deprecated: true is still accurate.
I'm just not sure I see the value personally. My brain runs on use-cases so if you could talk through a concrete instance where this would help I could come around?
What I like specifically about the "replacementOperationId" : "getFoo2" idea is that its basically adding onto deprecated: true and saying "aaaaand you should use this instead" which is handy, but it's not trying to get involved with a timeline in any way.
Maybe we do need a new issue so we don't overlap the ideas.
(Side note: https://datatracker.ietf.org/doc/html/rfc8594 specifies that the Sunset header contains a date, not a version.)
Could you help me with a use-case? What is the benefit of having sunset (a runtime HTTP header that talks about removal) in OpenAPI specifically?
I would use this to have my HTTP server automatically inject a Sunset header into the response when it sees this in the openapi document.
Another way to do it would be to tailor the application to look for this in the operation spec:
responses:
200:
headers:
Sunset:
description: injected into the response by the application
required: true
schema:
const: 2022-12-31 00:00:00
..and inject the header based on that, but it's more verbose.
(An openapi validator could also warn if the sunset date is in the past, indicating that the endpoint should be removed entirely.)
Could you help me with a use-case? What is the benefit of having sunset (a runtime HTTP header that talks about removal) in OpenAPI specifically?
You can already use
deprecated: trueto say "This is going away soon" and then the implementation is going to give you more specifics through the Deprecation and/or Sunset headers. The implementation may move the date back if not enough folks are doing it, and in that casedeprecated: trueis still accurate.I'm just not sure I see the value personally. My brain runs on use-cases so if you could talk through a concrete instance where this would help I could come around?
What I like specifically about the
"replacementOperationId" : "getFoo2"idea is that its basically adding ontodeprecated: trueand saying "aaaaand you should use this instead" which is handy, but it's not trying to get involved with a timeline in any way.Maybe we do need a new issue so we don't overlap the ideas.
Thanks @philsturgeon for your quick reply.
Here is the use case, say I've deprecated a couple of things in my specification: title field is deprecated in favor of name and route GET /something is deprecated.
There's no technical way of expressing the emergency of replacing title and getting rid foGET /something.
Using a new sunset property, I can explicitly tell that title will be definitely removed in version 3.0 while GET /something will be removed in version 4.0.
The benefits of this approach are that:
- consumers can prioritize their changes
- using linters and spec validators, API producers will never forget to remove deprecated stuff from their specs. When version will be bumped to 3.0, I'll get an error if I forget to remove the
titlefield, and on version4.0, I'll get an error forGET /something - it makes it easier to automate the backend and generate the
Sunsetheader. If I can tell some middleware in my framework that3.0will be released on 2023-01-01 and4.0on 2023-07-01, theSunsetheader could be added automatically based on that information (e.g. oh! you're callingGET /somethingbut it is deprecated and planned to be sunseted on3.0which is planned for 2023-01-01 so let me set theSunsetheader to2023-01-1.
Instead of "replacementOperationId" : "getFoo2", I would prefer
deprecatedBy:
- $ref: '#/paths/~1foo2/get'
or something like that. Using an array supports situations where one endpoint is split into two. By using JSON ref or similar, we provide a resolvable link to the new endpoint(s)