Every software designer agrees that design patterns, and solving familiar yet recurring design problems by implementing design patterns, are inevitable in the modern software design-and-development life cycle. These advanced patterns will help developers with the best-possible RESTful services implementation.
This article is an excerpt taken from the book, 'Hands-On RESTful API Design Patterns and Best Practices' written by Harihara Subramanian and Pethura Raj. In this book, design strategy, essential and advanced Restful API Patterns, Legacy Modernization to Micro services-centric apps are covered.
This article will help you understand the advanced patterns in RESTful API including Versioning, Authorization, Uniform contract, Entity endpoints, and many more.
The general rules of thumb we'd like to follow when versioning APIs are as follows:
There are four different ways that we can implement versioning in our API:
The major and minor version changes can be a part of the URI, for example, to represent v1 or v2 of the API the URI can be http://localhost:9090/v1/investors or http://localhost:9090/v2/investors, respectively.
The URI path versioning is a popular way of managing API versions due to its simple implementation.
The other simple method for implementing the version reference is to make it part of the request parameters, as we see in the following examples—http://localhost:9090/investors?version=1, http://localhost:9090/investors?version=2.1.0:
@GetMapping("/investors") public List<Investor> fetchAllInvestorsForGivenVersionAsParameter( @RequestParam("version") String version) throws VersionNotSupportedException { if (!(version.equals("1.1") || version.equals("1.0"))) { throw new VersionNotSupportedException("version " + version); } return investorService.fetchAllInvestors(); }
A custom header allows the client to maintain the same URIs, regardless of any version upgrades. The following code snippet will help us understand the version implementation through a custom header named x-resource-version. Note that the custom header name can be any name; in our example, we name it x-resource-version:
@GetMapping("/investorsbycustomheaderversion") public List<Investor> fetchAllInvestors...( @RequestHeader("x-resource-version") String version) throws VersionNotSupportedException { return getResultsAccordingToVersion(version); }
Providing the version information through the Accept (request) header along with the content-type (media) in response is the preferred way as this helps to version APIs without any impact on the URI. This is done by a code implementation of versioning through Accept and Content-Type:
@GetMapping(value = "/investorsbyacceptheader", headers = "Accept=application/investors-v1+json, application/investors-v1.1+json") public List<Investor> fetchAllInvestorsForGiven..() throws VersionNotSupportedException { return getResultsAccordingToVersion("1.1"); }
The right versioning is determined on a case-by-case basis. However, the content-negotiation and custom headers are a proponent of RESTful-compliant services.
How do we ensure our REST API implementation is accessible only to genuine users and not to everyone? In our example, the investor's list should not be visible to all users, and the stocks URI should not be exposed to anyone other than the legitimate investor. Here we are implementing simple basic authentication through the authorization header.
The basic authentication is a standard HTTP header (RESTful API constraint compliant) with the user's credentials encoded in Base64. The credentials (username and password) are encoded in the format of username—password. The credentials are encoded not encrypted, and it's vulnerable to specific security attacks, so it's inevitable that the rest API implementing basic authentication will communicate over SSL (https).
Securing the REST API with basic authentication is exceptionally simplified by the Spring security framework. Merely adding the following entries in pom.xml provides basic authentication to our investor service app:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
Now rebuild (mvn clean package) the application and restart it. It's time to test our APIs with the postman tool. When we hit the URL, unlike our earlier examples, we'll see an error complaining Full authorization required to access this resource
The preceding error is due to the addition of spring-security into our pom.xml file. We can access the REST API by observing a text using the default security password or search for it in our log file. That's the key for anyone to access our API.
We need to provide BasicAuth as the Authorization header for the API that we are accessing; we will see the results now without any authentication errors. Also the Authorization header that carries the XYZKL... token prefixed with Basic, as we use the HTTP Authentication header to enforce REST API authentication.
In many real-time situations, we need to use specific credentials to access the API and not the default one; in such cases, we can enhance our investor service application and secure it with our custom credentials by using few additional out-of-the-box spring modules.
In our investor service, we will have a new class, called PatronAuthConfig.java, which helps the app to enforce the credentials to the URLs that we would like to secure:
@Configuration @EnableWebSecurity public class PatronsAuthConfig extends WebSecurityConfigurerAdapter { .....
We can implement the security with a few annotations.
Services will always evolve with additional capabilities, enhancements, and defects fixes, however, now a service consumer can consume the latest version of our services without the need to keep changing their implementation or REST API endpoints. The uniform contract pattern comes to the rescue to overcome the problems. The pattern suggests the following measures:
If service clients want to interact with entities, such as investors, and their stocks without needing them to manage a compound identifier for both investor and stock, we need a pattern called entity endpoint. Entity endpoints suggest exposing each entity as individual lightweight endpoints of the service they reside in, so the service consumers get the global addressability of service entities
The entity endpoints expose reusable enterprise resources, so service consumers can reuse and share the entity resources. The investor service, exposes a couple of entity endpoints, such as /investors/investorId, and investor/stockId , and they are few examples of entity endpoints that service consumer can reuse and standardize.
Changing service endpoints isn't always ideal, However, if it needs to, will the service client know about it and use the new endpoint? Yes, with standard HTTP return codes, 3xx, and with the Location header, then by receiving 301 Moved permanently or 307 Temporary Redirect, the service client can act accordingly. The endpoint redirection pattern suggests returning standard HTTP headers and provides an automatic reference of stale endpoints to the current endpoints. The service consumers may call the new endpoints that are found in the Location header.
Imagine a bank's debit API failed immediately after deducting some amount from the client account. However, the client doesn't know about it and reissues the call to debit! Alas, the client loses money. So how can a service implementation handle messages/data and produce the same results, even after multiple calls?
Idempotent is one of the fundamental resilience and scalable patterns, as it decouples the service implementation nodes across distributed systems. Whether dealing with data or messages, the services should always have designed for sticking to Idempotent in nature.
There is a simple solution: use the idempotent capabilities of the HTTP web APIs, whereby services can provide a guarantee that any number of repeated calls due to intermittent failures of communication to the service is safe, and process those multiple calls from the server without any side effects.
Marking a list of emails as read in our email client could be an example of a bulk operation; the customer chooses more than one email to tag as Read, and one REST API call does the job instead of multiple calls to an underlying API.
The following two approaches are suggested for implementing bulk operations:
The bulk operations may involve many other aspects, such as E-tag, asynchronous executions, or parallel-stream implementation to make it effective.
The circuit breaker is an automatic switch designed to protect entire electrical circuits from damage due to excess current load as a result of a short circuit or overload. The same concept applies when services interact with many other services. Failure due to any issue can potentially create catastrophic effects across the application, and preventing cascading impacts is the sole aim of a circuit-breaker pattern. Hence, this pattern helps subsystems to fail gracefully and also prevents complete system failure as a result of a subsystem failures.
There are three different states that constitute the circuit breaker:
There's a new service called circuit-breaker-service-consumer, which will have all the necessary circuit-breaker implementations, along with a call to our first service.
As software designers, we understand the importance of gracefully handling application failures and failure operations. We may achieve better results by combining the retry pattern and the circuit breaker pattern as it provides the application with greater flexibility in handling failures.
The retry patterns enable the application to retry failed operations, expecting those operations to become operational and eventually succeed. However, it may result in a denial of service (DoS) attack within our application.
API facade abstracts the complex subsystem from the callers and exposes only necessary details as interfaces to the end user.
The client can call one API facade to make it simpler and more meaningful in cases where the clients need multiple service calls. However, that can be implemented with a single API endpoint instead of the client calling multiple endpoints. The API facades provide high scalability and high performance as well.
The investor services have implemented a simple API facade implementation for its delete operations. As we saw earlier, the delete methods call the design for intent methods. However, we have made the design for the intent method abstract to the caller by introducing a simple interface to our investor services. That brings the facade to our API.
The interface for the delete service is shown as follows:
public interface DeleteServiceFacade { boolean deleteAStock(String investorId, String stockTobeDeletedSymbol); boolean deleteStocksInBulk(String investorId, List<String> stocksSymbolsList); }
Backend for frontend (BFF) is a pattern first described by Sam Newman; it helps to bridge any API design gaps. BFF suggests introducing a layer between the user experience and the resources it calls. It also helps API designers to avoid customizing a single backend for multiple interfaces. Each interface can define its necessary and unique requirements that cater to frontend requirements without worrying about impacting other frontend implementations.
BFF may not fit in cases such as multiple interfaces making the same requests to the backend, or using only one interface to interact with the backend services.
So caution should be exercised when deciding on separate, exclusive APIs/interfaces, as it warrants additional and lifelong maintenance, security improvement within layers, additional customized designs that lead to lapses in security, and defect leaks.
In this article, we have discussed versioning our APIs, securing APIs with authorization, and enabling the service clients with uniform contract, entity endpoint, and endpoint redirection implementations. We also learned about Idempotent and its importance, which powers APIs with bulk operations. Having covered various advanced patterns, we concluded the article with the circuit breaker and the BFF pattern. These advanced pattern's of restful API's will provide our customers and app developers with the best-possible RESTful services implementation.
To know more about the rules for most common resource formats, such as JSON and hypermedia, and error types, in brief, client concerns, head over to the book, 'Hands-On RESTful API Design Patterns and Best Practices'.
Inspecting APIs in ASP.NET Core [Tutorial]
Google announces the general availability of a new API for Google Docs
The tug of war between Google and Oracle over API copyright issue has the future of software development in the crossfires