I want to identify what might be considered as a best practice for URI versioning of the APIs, regarding the logic of the back-end implementation.
Let's say we have a java application with the following API:
http://.../api/v1/user
Request:
{
"first name": "John",
"last name": "Doe"
}
After a while, we need to add 2 more mandatory fields to the user API:
http://.../api/v2/user
Request:
{
"first name": "John",
"last name": "Doe",
"age": 20,
"address": "Some address"
}
We are using separate DTOs for each version, one having 2 fields, and another having 4 fields.
We have only one entity for the application, but my question is how we should handle the logic, as a best practice? Is ok to handle this in only one service?
If those 2 new fields "age" and "address" would not be mandatory, this would not be considered a breaking change, but since they are, I am thinking that there are a few options:
If I use only one manager for all user API versions and put there some constraints/validations, V2 will work, but V1 will throw an exception because those fields are not there.
I know that versioning is a big topic, but I could not find a specific answer on the web until now. My intuition says that having a single manager for all user API versions will result in a method that has nothing to do with clean code, and also, I am thinking that any change added with a new version must be as loosely coupled as possible, because will be easier to make older methods deprecated and remove them in time.
You are correct in your belief that versioning with APIs is a contentious issue. You are also making a breaking change and so incrementing the version of your API is the correct decision (w.r.t. semver).
Ideally your backend code will be under version control (eg GitHub). In this case you can safely consider V1
to be a specific commit in your repository. This is the code that has been deployed and is serving traffic for V1
. You can then continue making changes to your code as you see fit. At some point you will have added some new breaking changes and decide to mark a specific commit as V2
. You can then deploy V2
alongside V1
. When you decide to depreciate V1
you can simply stop serving traffic.
You'll need some method of ensuring only V1
traffic goes to the V1
backend and V2
to the V2
backend. Generally this is done by using a Reverse Proxy; popular choices include NGINX and Apache. Any sufficient reverse proxy will allow you to direct requests based on the path such that if the request is prefixed by /api/v1
then redirect that request to Backend1
and if prefixed by /api/v2
to Backend2
.
Hopefully this model will help keep your code clean: the master
branch in your repository only needs to deal with the most recent API. If you need to make changes to older API versions this can be done with relative ease: branch off the V1
commit, make your changes, and then define the HEAD
of that modified branch as the 'new' V1
.
A couple of assumptions about your backend have been made for this answer that you should be aware about. Firstly, your backend can be scaled horizontally. For example, this means that if you interact with a database then the multiple versions of your API can all safely access the database concurrently. Secondly, that you have the resources do deploy replica backends.
Hopefully that explanation makes sense; but if not any questions send them my way!
If you're able to/can entertain code changes to your existing API, then you can refer to this link. Also, the link's mentioned at the bottom of the post direct you to respective GitHub source code which can be helpful in case if you think to introduce the code changes after your trial-error.
The mentioned approach(using @JsonView) basically prevents one from introducing multiple DTO's of a single entity for the same/multiple clients. Eventually, one can also refrain from introducing new version APIs each & every time you introduce new fields in your existing API.
spring-rest-jackson-jsonview
jackson-jsonview
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With