API evolutions

Systems evolve over time, and the functionality that they expose changes along with these evolutions. APIs, be it in the form of a library or a RESTful API, must adapt to these changes while maintaining some form of backward compatibility.

It is therefore a good idea to consider evolutions when designing APIs. The next section describes how HATEOAS (Hypermedia As The Engine Of Application State) can be leveraged to manage evolutions.

HATEOAS

This REST principle provides a method for self-discovery for service consumers. Let's consider the following response to a RESTful endpoint:

{
  "id": 1,
  "category": "http://myservice.com/categories/33",
...
}

This response includes a hypermedia link to a related resource, so consumers do not need prior knowledge of where to fetch the resource from.

With this approach, service designers can manage evolutions by introducing new hypermedia links as changes occur, and retiring old links without requiring service consumers to be modified. It is a powerful mechanism that can help effectively manage service evolutions.

Versioning strategies

While the purists will disagree with the use of versioning in RESTful web services, in real world situations, versioning provides a useful mechanism to manage major evolutions of an API. With resources being at the center of REST, a natural place for versioning is in the URI.

URI versioning

Let's consider the following URI /rooms/{roomId} that provides access to a room resource, as described in The Data-Transfer-Object design pattern section. We want to add a new field containing a URL pertaining to a picture of the room (pictureUrl). Such a change is forward-compatible and does not require specific treatment for clients to continue consuming the service.

Now, we realize that having more than one picture would improve user engagement. We could simply add a new field along with the existing picture URL. However, it makes more sense to just replace the picture URL field with a list of URLs (pictureUrls). This change is not backward compatible. Therefore, we need to create a new version of our service to ensure that existing clients will continue working. To do so, we include the version in resource URIs.

With this approach, /rooms/{roomId} becomes /v{versionNumber}/rooms/{roomId}. For example, the URI for a room with ID #1 in our first API is /v1.0/rooms/1, and it will contain the pictureUrl field. The room in our new version will be accessible with /v1.1/rooms/1 and return the list of picture URLs.

Tip

A good practice with URI-based versions is to alias non-version URIs to the latest API version. So, if the latest version of our API is 2.0, /rooms/1 should be an alias of /v2.0/rooms/1.

Representation versioning

In the previous example, we produced different representations of our Rooms. We could therefore consider managing versioning with MIME type versioning. The following code snippet illustrates how we can achieve this with Spring Web:

@RequestMapping(value = "/{roomId}", method = RequestMethod.GET)
public RoomDTO getRoom(@PathVariable("roomId") long id) {
  Room room = inventoryService.getRoom(id);
  return new RoomDTO(room);
}

@RequestMapping(value = "/{roomId}", method = RequestMethod.GET, consumes = "application/json;version=2")
public RoomDTOv2 getRoomV2(@PathVariable("roomId") long id) {
  Room room = inventoryService.getRoom(id);
  return new RoomDTOv2(room);
}

With this setup, getRoomV2() will be invoked when requests have their Content-Type headers set to application/json;version=2. Otherwise, getRoom() will handle requests for the access of rooms.

Other approaches

Besides the two approaches described previously, one could consider using other solutions. For example, a dedicated header, such as X-API-Version, could be used to specify which API version should be used. The following code will handle requests to get rooms for version 3 of our API:

@RequestMapping(value = "/{roomId}", method = RequestMethod.GET, headers = {"X-API-Version=3"})
public RoomDTOv3 getRoomV3(@PathVariable("roomId") long id) {
  Room room = inventoryService.getRoom(id);
  return new RoomDTOv3(room);
}

In this example, we instructed Spring to map requests to /rooms/{roomId} containing the header, X-API-Version, with a value of 3 for this method.

Whichever technique one chooses to employ, API designers should bear the following principles in mind:

  • Expose only what is required; maintaining backward compatibility is hard work.
  • Favor forward-compatible changes over breaking ones. It is not always possible to force clients to upgrade to new API versions.
  • From the onset, design APIs with support for evolutions. Depending on the selected approach, back-porting such support into an API might not be straightforward.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.71.28