2

I have a spring boot rest API with following endpoints:

myapi/v1/auth

myapi/v1/car

myapi/v1/part

myapi/v1/history

Each of these (except authentication) have 4 controller methods representing GET, POST, PUT, DELETE CRUD operations.

Let's say I have a breaking change that requires modification only for the /car POST endpoint.

To handle the breaking change, I introduce new version for the /car endpoint. I do this by adding a new controller with @RestController annotation mapping to new path:

myapi/v2/car

I'm my opinion, if other endpoints are not affected by this breaking change, they should stay at /v1 endpoint and only /car should get new /v2 endpoint in addition to having its old /v1 endpoint.

As a result, I think my endpoints should now look like:

myapi/v1/auth

myapi/v1/car

myapi/v2/car

myapi/v1/part

myapi/v1/history

My colleague is challenging this and claims that ALL endpoints should get /v2 as well even though they are not affected by the breaking change.

Which one is correct?

pixel
  • 9,653
  • 16
  • 82
  • 149

2 Answers2

1

There is no black and white answer. The answer is always "it depends". In this case, it depends on how you want to do your API versioning. All of them have advantages and disadvantages. I personally prefer to not use /v1 /v2 stuff at all, but implement a MIME-type that specify which type of /car endpoint they should call. So you are very fine granular by making breaking changes. A brief overview what CAN be done can be found here.

Ditscheridou
  • 381
  • 1
  • 5
  • 18
  • It is very clear from the question what versioning scheme I'm using. Please read the question before answering with unrelated content – pixel Mar 29 '23 at 13:46
  • This is not unrelated at all and my answer is completely valid to your question. – Ditscheridou Mar 30 '23 at 07:41
1

@Ditscheridou is correct that there is no single, universal answer. The most appropriate answer is very contextual and depends on the specific use case.

First, it should be clarified that a change, any change, should be considered a breaking change. The established contract is the contract and cannot be broken. Backward compatibility is a fallacy. Many people assume that additive changes are backward compatible, but the server cannot guarantee this is true. Most clients will use a Tolerant Reader to handle changes, but the server cannot be certain a client has done so. The only exception to these rules is if you own both sides and the API is not public. In that case, you know and control what the client can or cannot handle. There are clients out there that perform strict validation in their integration tests that fail if something is missing or something extra is added.

An API version is not like a binary version so you should not apply the binary compatibility rules to a HTTP-based API. Despite being called a version, an API version is much more like a media type as @Ditscheridou eluded to, which is why that is the only method of versioning that Fielding himself has said is valid. You've clearly already gone down the path of versioning by URL segment, so I won't attempt to convince you otherwise. However, you should know that, although it is a popular versioning method, it is not RESTful; it violates the Uniform Interface constraint. For example, myapi/v1/car/42 and myapi/v2/car/42 are not two different cars, they are two different representations. Different representations are facilitated by media type negotiation. The Uniform Interface states that the URL path is the identifier, contrary to some people believing it would just be 42. It might very well be in some backend data store, but that is an implementation detail. This ultimately means that two different URLs implies two different resources (e.g. cars), which is not the case.

This isn't just some REST and HTTP dogma. This is how it was meant to be implemented. The GitHub API is an well-known example of an API that versions by media type.

The scenario you have described is what I call Symmetrical Versioning. Strictly speaking, this is absolutely not required and there is no reason to not allow services to evolve independently with different versions. The main reason to have such a policy is to make it convenient for clients. It is not so convenient for the server. Every change, to any service, pushes the version forward for every service even if there is no change. There are ways to make the implementation more manageable, but you have to be careful that a change in one version isn't accidentally propagated to another. You'll also want a formal policy, if you don't have one already, such as N-2 to prevent supported versions from growing uncontrollably.

Another problem that you will encounter, if you're using it or care, is HATEOAS. By choosing to put the version in the URL segment, if a client asks for myapi/v2/car/42 and it has hypermedia links to myapi/v1/part/1, how does the Car service know that? What if the Parts service has versions v1 and v3, but the Car service only has v1 and v2? Symmetric Versioning can partially help with this problem because - by policy - it can be assumed that all APIs have an implementation of the same version. However, if your API is public, then there is no way to ensure that it aligns to the client. What if the client only understands myapi/v2/car/42 and myapi/v3/part/1? This is a very real possibility when the versions are asymmetrical. The server should never assume the API version a client wants; the client should always have to explicitly ask for it. This problem is unique to versioning by URL segment. Other methods do not have this issue because the URLs would always be stable (e.g. myapi/car/42 and myapi/part/1) and the client would indicate the representation they want by way of query string, header, or media type. This is no different than the client asking for application/xml versus application/json.

So which approach is the right way? It depends. There are pros and cons to each approach. Keeping the versions symmetrical will solve some problems, but could make it harder to manage; especially, if you ever break up the codebase. Do all the versions need to be symmetrical, even if there is no change - no. If you provide a managed client for your API, that is another way you can help clients avoid the challenges of aligning versions. Furthermore, you might version the client in a consistent, linear way even though the API versions are asymmetrical behind the scenes. This is a common approach and the consumer of the managed client library is never the wiser to the underlying API versions.

Chris Martinez
  • 3,185
  • 12
  • 28
  • Thanks for the clarification and detail. Maybe I wasn't clear enough but I have no choice but using versioning url as I showed in the question. In addition, no RESTful API, it is just RESTAPI. Finally, the question is if only ```/car``` endpoint code changed and got ```/v2```, do all others have to get ```/v2``` as well evne though they did not change? Hope that clarifies. Beside, both API and client is internal, not public facing – pixel Mar 30 '23 at 22:31
  • 1
    No - they do not all need to change. IMO, I would recommend having an managed client. That client can handle all of the knowledge and understanding of which service is which version. This will keep it simple and consistent for clients, while still keeping it manageable on the server side. Just because you make the server API version symmetrical doesn't mean clients will consume them symmetrically (e.g. `/v2/car` with `/v1/part`). It may not be so easy to push clients to newer versions either. A managed client can be a way to centrally facilitate that across the board. – Chris Martinez Mar 31 '23 at 17:19