Sådan versioneres en REST API

Hvis du ikke er meget fortrolig med API'er, spekulerer du måske på ... hvorfor al den ståhej om API-versionering?

Hvis du er blevet brændt af API-ændringer, er du sandsynligvis den, der travler. Hvis du er vedligeholder af en API, kan du muligvis også bryde dig om at prøve at stille udfordrende spørgsmål som disse:

# Is this version 2 of just products or of the entire API? /v2/products # What catalyzed the change between v1 and v2? How are they different? /v1/products /v2/products

Disse spørgsmål omkring versionering er ikke lette at besvare. Det er ikke altid klart, hvad v1eller hvad v2der refererer til. Og vi skal ikke bare lave en anden version af et slutpunkt, når den første ikke længere synes at være tilstrækkelig.

Der er klare grunde til, at din API skal have versionversion, og der er klare strategier for, hvordan du effektivt navigerer i API-ændringer.

Imidlertid har jeg fundet ud af, at de fleste udviklere - inklusive mig selv, indtil jeg lærte nogle lektioner på den hårde måde - ikke er opmærksomme på disse grunde og strategier.

Denne artikel søger at fremhæve disse grunde til versionering og strategier til at gennemføre den. Vi antager en REST API-kontekst, da det er en standard for mange API'er og fokuserer på versioneringsaspektet .

Hvad er versionering?

Vi bør starte med niveauindstilling på, hvad der menes med udtrykket "API versioning". Her er vores arbejdsdefinition:

API-versionering er praksis med transparent styring af ændringer i din API.

Versioning er effektiv kommunikation omkring ændringer i din API, så forbrugerne ved, hvad de kan forvente af den. Du leverer data til offentligheden på en eller anden måde, og du skal kommunikere, når du ændrer den måde, data leveres på.

Hvad dette koger ned til, i den nitty gritty, er at styre datakontrakter og bryde ændringer. Førstnævnte er den primære byggesten i din API, og sidstnævnte afslører, hvorfor versionering er nødvendig.

Datakontrakter

En API er en Application Programming interface , og en grænseflade er en fælles grænse for informationsudveksling. Datakontrakten er kernen i denne grænseflade.

En datakontrakt er en aftale om formen og det generelle indhold af anmodningen og / eller svardataene.

For at illustrere en datakontrakt er her et grundlæggende JSON-svarorgan:

{ "data": [ { "id": 1, "name": "Product 1" }, { "id": 2, "name": "Product 2" } ] }

Det er et objekt med en dataegenskab, der er en matrix (liste) med produkter, hver med en idog nameegenskab. Men dataejendommen kunne lige så let have været kaldt body, og idejendommen på hvert produkt kunne have været en GUID i stedet for et heltal. Hvis et enkelt produkt blev returneret, datakunne det være et objekt i stedet for et array.

Disse tilsyneladende subtile ændringer ville have gjort for en anden aftale, en anden kontrakt med hensyn til "formen" på dataene. Dataformen kan gælde for ejendomsnavne, datatyper eller endda det forventede format (JSON vs. XML).

Hvorfor er versioning nødvendig?

Med API'er kan noget så simpelt som at ændre et ejendomsnavn fra productIdtil productIDbryde ting for forbrugerne. Denne ting skete med vores hold i sidste uge.

Heldigvis havde vi test for at fange ændringer i API-kontrakten. Vi skulle dog ikke have haft brug for disse tests, fordi vedligeholdere af API skulle have vidst, at dette ville være en brudende ændring.

Breaking Changes

Dette var en brudende ændring af den aftalte datakontrakt, fordi deres ændring også tvang os til at ændre vores ansøgning.

Hvad udgør en "breaking change" i et API-slutpunkt? Enhver ændring af din API-kontrakt, der tvinger forbrugeren til også at foretage en ændring.

Breaking ændringer passer primært ind i følgende kategorier:

  1. Ændring af formatet for anmodning / svar (f.eks. Fra XML til JSON)
  2. Ændring af et egenskabsnavn (f.eks. Fra nametil productName) eller datatype på en egenskab (f.eks. Fra et heltal til en float)
  3. Tilføjelse af et påkrævet felt på anmodningen (f.eks. En ny påkrævet overskrift eller egenskab i en anmodningsinstans)
  4. Fjernelse af en ejendom på svaret (f.eks. Fjernelse descriptionfra et produkt)

API Change Management

Det er aldrig klogt eller venligt at tvinge forbrugerne af en API til at foretage en ændring. Hvis du skal foretage en skiftende ændring, er det, hvad versionering er beregnet til, og vi dækker de mest effektive måder til version af din applikation og slutpunkter.

Men lad os først kort diskutere, hvordan man undgår at bryde ændringer i første omgang. Vi kunne kalde denne API-ændringsstyring.

Effektiv ændringsstyring i forbindelse med en API opsummeres af følgende principper:

  • Fortsæt support til eksisterende egenskaber / slutpunkter
  • Tilføj nye egenskaber / slutpunkter i stedet for at ændre eksisterende
  • Tankevækkende solnedgang forældede egenskaber / slutpunkter

Her er et eksempel, der demonstrerer alle tre af disse principper i forbindelse med svaret på anmodning om brugerdata:

{ "data": { "id": 1, "name": "Carlos Ray Norris", // original property "firstName": "Carlos", // new property "lastName": "Norris", // new property "alias": "Chuck", // obsolete property "aliases": ["Chuck", "Walker"] // new property }, "meta": { "fieldNotes": [ { "field": "alias", "note": "Sunsetting on [future date]. Please use aliases." } ] } }

I dette eksempel namevar en original ejendom. De firstNameog lastNamemarker er ved at blive implementeret for at give en mere detaljeret indstilling, i tilfælde af at forbrugeren ønsker at vise "Mr. Norris" med nogle snor interpolation, men uden at skulle parse namemarken. Men den namevil ejendommen blive støttet i en løbende mode.

aliasderimod vil blive udfaset til fordel for aliasesarrayet - fordi Chuck har så mange aliaser - og der er en note i svaret for at angive tidsrammen for solnedgangen.

Hvordan versionerer du en API?

Disse principper vil tage langt i at navigere i ændringer til din API uden at skulle rulle en ny version. Imidlertid kan det nogle gange undgås, og hvis du har brug for en helt ny datakontrakt, skal du bruge en ny version af dit slutpunkt. Så du bliver nødt til at kommunikere det til offentligheden på en eller anden måde.

Bemærk, at vi ikke taler om versionen af ​​den underliggende kodebase. Så hvis du bruger semantisk versioning til din applikation, der også understøtter en offentlig API, vil du sandsynligvis ønske at adskille disse versioneringssystemer.

How do you create a new version of your API? What are the different methods for doing so? You'll need to determine what type of versioning strategy you want to take in general, and then as you develop and maintain your API, you'll need to determine the scope of each version change.

Scope

Let's tackle scope first. As we explored above, sometimes data contracts will be compromised by a breaking change, and that means we'll need to provide a new version of the data contract. That could mean a new version of an endpoint, or it could mean a change at a more global application scope.

We can think of levels of scope change within a tree analogy:

  • Leaf - A change to an isolated endpoint with no relationship to other endpoints
  • Branch - A change to a group of endpoints or a resource accessed through several endpoints
  • Trunk - An application-level change, warranting a version change on most or all endpoints
  • Root - A change affecting access to all API resources of all versions

As you can see, moving from leaf to root, the changes become progressively more impactful and global in scope.

The leaf scope can often be handled through effective API change management. If not, simply create a new endpoint with the new resource data contract.

A branch is a little trickier, depending on just how many endpoints are affected by the data contract change on the resource in question. If the changes are relatively confined to a clear group of related endpoints, you could potentially navigate this by introducing a new name for the resource and updating your docs accordingly.

# variants, which has a breaking change, is accessed on multiple routes /variants /products/:id/variants # we introduce product-variants instead /product-variants /products/:id/product-variants

A trunk refers to application-level changes that are often a result of a change in one of the following categories:

  • Format (e.g. from XML to JSON)
  • Specification (e.g. from an in-house one to JSON API or Open API)
  • Required headers (e.g. for authentication/authorization)

These will necessitate a change in your overall API version, so you should plan carefully and execute the transition well.

A root change will force you to go one step further in ensuring that all consumers of all versions of your API are aware of the change.

Types of API Versioning

As we turn to different types of API versioning, we'll want to use these insights into varying scopes of API changes to evaluate the types. Each approach has its own set of strengths and weaknesses in addressing changes based on their scope.

There are several methods for managing the version of your API. URI path versioning is the most common.

URI Path

//www.example.com/api/v1/products //api.example.com/v1/products

This strategy involves putting the version number in the path of the URI, and is often done with the prefix "v". More often than not, API designers use it to refer to their application version (i.e. "trunk") rather than the endpoint version (i.e. "leaf" or "branch"), but that's not always a safe assumption.

URI path versioning implies orchestrated releases of application versions that will require one of two approaches: maintaining one version while developing a new one or forcing consumers to wait for new resources until the new version is released. It also means you'd need to carry over any non-changed endpoints from version to version. However, for APIs with relatively low volatility, it's still a decent option.

You would likely not want to relate your version number to that of the endpoint or resource, because it would easily result in something like a v4 of products but a v1 of variants, which would be rather confusing.

Query Params

//www.example.com/api/products?version=1

This type of versioning adds a query param to the request that indicates the version. Very flexible in terms of requesting the version of the resource you'd like at the "leaf" level, but it holds no notion of the overall API's version and lends itself to the same out-of-sync issues mentioned in the above comment on endpoint-level versioning of the URI path.

Header

Accept: version=1.0

The header approach is one that provides more granularity in serving up the requested version of any given resource.

However, it's buried in the request object and isn't as transparent as the URI path option. It's also still hard to tell whether 1.0 refers to the version of the endpoint or the API itself.

Integrating Types

Each of these approaches seem to have the weakness of either favoring a "leaf" or "trunk" scope, but not supporting both.

If you need to maintain the overall API version and also provide support for multiple versions of resources, consider a blend of the URI Path and Query Params types, or a more advanced Header approach.

# URI path and query params combo //api.example.com/v1/products?version=1 //api.example.com/v1/products?version=2 # Extended headers, for //api.example.com/products Accept: api-version=1; resource-version=1 Accept: api-version=1; resource-version=2

Conclusion

We've covered a lot of ground here, so let's recap:

  • API versioning is the practice of transparently managing changes to your API.
  • Managing an API boils down to defining and evolving data contracts and dealing with breaking changes.
  • Den mest effektive måde at udvikle din API uden at bryde ændringer er at følge effektive API-ændringsstyringsprincipper.
  • For de fleste API'er er versionering i URI-stien den mest enkle løsning.
  • For mere komplekse eller flygtige API'er kan du styre forskellige omfang af ændringer ved at anvende en integration af URI-sti og forespørgsler om params-tilgange.

Selv om disse principper skal give en klar retning i, hvordan man effektivt styrer ændringer i dine API'er, er det potentielt mere kunst end videnskab at udvikle en API. Det kræver tanke og fremsyn for at skabe og vedligeholde en pålidelig API.