Lesson 3: Dealing with Changes in the API - URL Changes
1. Goal
This lesson aims to explore the strategies for managing API URL changes in Spring Boot applications, focusing on minimizing disruptions for client applications.
In this lesson, we’ll discuss various approaches to make this transition smooth and backward-compatible.
2. Lesson Notes
The relevant module you need to import when you're starting with this lesson is: dealing-with-changes-in-the-api-url-changes-start.
If you want to have a look at the fully implemented lesson as a reference, feel free to import: dealing-with-changes-in-the-api-url-changes-end.
Additionally, before getting started, let’s import the Postman collection from our “main” branch, referring particularly to the “Evolve the Rest API/Dealing with Changes in the API - URL Changes” folder.
2.1. URL Changes
Let's imagine there’s a requirement to change from “/workers” to “/employees” for improved domain semantic clarity.
Of course, simply changing the URL would break clients consuming these endpoints. One option to ensure a smooth transition is to keep both paths functional for a period of time, so that clients have time to transition to the new URL path.
We’ll navigate to the “WorkerController” class to see how we can implement this in our application:
As we can see, this is a very simple change using Spring’s @RequestMapping annotation, which can accept an array of URL paths.
We can now start our application, and confirm the application responds correctly to both paths using Postman:
Postman Request: Get Worker
Postman Request: Get EmployeeAnd, of course, it’s important to let the Clients know about this new available endpoint in the API documentation. In our case, we can confirm the OpenAPI web documentation is updated automatically by browsing http://localhost:8080/swagger-ui/index.html:
2.2. Different Param Handling for URL Changes
Let’s explore a new use case here, assuming there’s a requirement to access “tasks” via a “campaign” as a nested resource with a path: /campaign/<campaignId>/tasks.
Supporting both endpoints without repeating code, similar to the last case, is possible, but the situation presents a more complex issue due to different parameter handling.
We can introduce a strategy using optional parameters in Spring Web to deal with this. This approach maintains support for both the old and new URL structures without breaking existing client implementations, while assuring that the implemented functionality is the same for both routes.
Specifically, Spring allows making certain parts of the URL optional by setting the “required” attribute to “false” in the “@PathVariable” or “@RequestParam” annotations.
Let’s navigate to the “TaskController” class and refactor the searchTasks method to add the new mapping:
Note that for this change, we had to remove the class-level “@RequestMapping(value = “/tasks”)” annotation, since not all the nested paths comply with this base pattern now. As a consequence, we had to include it in the rest of the pre-existing paths.
Service and Repository Layer Updates
Occasionally, URL changes require adapting our code as well. In this case, for example, the campaignId will represent an additional filtering condition for retrieved tasks.
This will surpass the web/presentation layer, since it requires updating our service and repository components too. We won’t go into further details here though, as it’s irrelevant to the scope of this lesson. Instead, we’ll straightforwardly point out the changes we have to perform to our application.
With this, we’ll be able to fully appreciate the impact that a small URL change might have.
Let's start by updating the query method in the “TaskRepository” class:
Next, we'll update the “searchTasks” method in the “TaskService” interface:
Then we’ll update the corresponding “DefaultTaskService” class:
Finally, we can update the TaskController’s searchTasks method to use the updated service method:
We can now restart our application, and confirm the application responds correctly to both endpoints using Postman:
Postman Request: List of Tasks
Postman Request: List of Tasks via Campaign
Once again, we can find the new endpoint in the API documentation:
2.3. URL Deprecation
Depending on the situation and business needs, we may opt to support both old and new endpoints for good (if we want to support both accessing Tasks through the associated Campaign and a plain “/tasks” search endpoint) or just temporarily.
If it’s just a transitional phase, it’s good practice to mark the old endpoints as deprecated in the documentation, so that consumers can be aware and adapt their services towards the new endpoint.
Additionally, to complement the deprecation process and provide clients with more detailed guidance, we can incorporate the Sunset HTTP response header field. This header field allows a server to communicate the fact that a resource is expected to become unresponsive at a specific point in time.
We’ll now deprecate the old GET “/tasks” search endpoint and point out a caveat here. When using Spring’s annotations, like “@RequestMapping” or “@GetMapping”, to define multiple paths for the same controller method, it’s impossible to mark just one of those paths as deprecated.
To overcome this limitation, we’ll have to define a new method for the new URL, even if they represent the same functionality. Though this may result in code duplication, it provides a clear path for URL deprecation.
Let’s modify the “TaskController”:
- marking the pre-existing endpoint as deprecated
- updating its mapping annotation to use just the old path
- copy-pasting it as a new method for the new “search by Campaign” feature
- but moving the common logic to a private method for a DRYer approach
- adding a future HTTP-date timestamp as the sunset header in the response
- and finally, removing the campaignId param from the old method just because it’s not used:
As we can see, we have two options to mark the endpoint as “deprecated” here. We can use the regular @Deprecated annotation, but if we want to guide the clients in the transition, it might be better to use the @Operation Swagger annotation, where we can add a description message.
Let’s restart the application, and confirm that the API documentation is showing the API as deprecated and retrieving the Sunset header field:
We won’t go into much detail regarding the Sunset header RFC (we provide a link in the Resources section for that), but let's mention a few important points:
- Clients should take the date provided in the Sunset header only as a hint
- the spec also defines a Link relation type that can be utilized to provide additional information, and this could be provided using Hypermedia/HATEOAS mechanisms
- the sunsetting should be interpreted by clients only for the requested resource, but increased scope can be defined and communicated for clients to behave accordingly
2.4. Leveraging HATEOAS
We’ll briefly analyze how HATEOAS can help with the URL updates with minimal disturbances here.
We won’t go into the details of the implementation. Instead, we’ve included a working solution in the End project of this lesson that you can check out and try yourself.
For instance, let’s analyze the implications of the information retrieved by the now deprecated GET /tasks response using the End module:
Postman Request: List of Tasks - HATEOAS
- If the consumer is hypermedia-driven, then switching the /workers endpoints to /employees would need absolutely no change on the client application side, since the links are discovered and used from the response itself.
- HATEOAS allows indicating when a link is deprecated, providing a URL with further information. Specs like HAL indicate that services traversing a deprecated link should notify the maintainer in order to act upon this.
- The links themselves guide how to transition to alternative requests, as in this case, where it points out how a new “search” call can be formulated.
3. Resources
- Getting Started with Spring HATEOAS