Disclaimer: It has been taken from my book – “Cloud Native Microservices using Spring and Kubernetes“.
Application Programming Interface (API) allows two apps/resources to talk to each other and is mostly referred for Service Oriented Architecture (SOA)
API is gaining more popularity when microservices development is booming for modern cloud-native applications or app modernization. We can’t imagine microservices without APIs, because there are so many distributed services in a microservice architecture, which can’t be easily integrated without the help of API. So, both Microservices and API compliments each other!
It’s an architectural design specification, a set of protocols that provides an interface to integrate and talk different microservices/monolithic apps and databases with each other. API does talk about how external services can communicate with apps, not how it works!
It creates an integration contract between different apps/external clients with a standard set of rules and specifications. It’s followed as a development practice for external clients/apps.
API is based on a contract first design pattern of development, where all developments happen around APIs specifications and protocols. Developers use the same standard practices across different microservices agile development teams.
API best practices
Now, we will discuss a few best API practices in detail:
- Follow OpenAPI standard: Modern apps API should follow OpenAPI specification to make it compatible and portable for all kinds of apps.
- API web dashboard support: It should be developer-friendly with the API management dashboard, which helps to create, manage, and monitor APIs in large systems or microservices environments. There are many API open source and enterprise solutions like OpenAPI based SwaggerHub, Google Apigee, and so on. They provide a web-based dashboard to manage APIs dynamically and can be exported as source code and shared with development teams.
- Web-based HTTP with REST: Most of the apps, databases, and messaging systems use REST over HTTP protocol communication over the internet. REST is widely accepted, supported by most of the clients and logical integration apps, and so on. It’s more flexible, has rich features. If you are building an API then you should know the basics about HTTP web protocol and its methods, attributes, and status codes. Also, you should have a good understanding of the REST style of API interfaces, because REST is resource oriented architectural style.
- Return valid structured JSON response: Don’t return plain text message. It should be well-structured JSON, XML, or a similar response. Example is as follows:
“fullProductName”: “LG 50B6000FHD 127 Fridge”,
- Maintain status codes: API must return HTTP status codes because when a client sends a request to server through REST API, it expects response from the server, if it’s a success or failure. There are standard pre-defined error codes for this purpose:
|2xx||Success category, for example, 200 – Ok|
|3xx||Redirection category, for example, 304 – Not modified|
|4xx||Client error category, for example, 404 – Not found|
|5xx||Server errors category, for example, 500 – Internal server error|
- API Endpoint naming standard: Name the collections using plural nouns. The reason behind this, the same resource can return a single record or multiple records. It’s not recommended to have two separate resources URIs for these two resources. For example, /orders is a valid URI name for API, which serves both purposes.
Use nouns instead of verbs. It will be a standard naming convention, because multiple operations can be done on a single resource or object. For example, /orders is a noun and correct way because order can be created, updated, deleted, and fetched. It’s not recommended to use / createOrder, /updateOrder, /deleteOrder, and so on.
- Error handling, return error details with error code: Server resource should always returns appropriate error code, internal error code and simple human-readable error message for better error and exception handling at client-side apps, for example:
“errorDetail”: “Connection refused”
- Return appropriate HTTP response status code: Every REST endpoint should return a meaningful HTTP response code to handle server responses in a better way like:
- 200 for success.
- 404 for not found.
- 201 resource is created.
- 304 not modified. Response already in its cache.
- 400 bad request. The client request was not processed, as the server could not understand what the client was asking for.
- 401 unauthorized. Not allowed to access resources, and should re-request with the required credentials.
- 403 forbidden. Client is authenticated, but the client is not allowed access to the page or resource for some reason.
- 503 services unavailable. The server is down or unavailable to receive and process the request.
- Avoid nesting of related resources: Sometimes, resources are related to each other like /orders resource object is related to catalogue category and user ID. We should not nest resources like: GET /orders/mobile/111
It’s recommended to use top-level resource and make other related resources as a query parameter like this:
- Handle trailing slashes: It’s always advisable to use only one approach either with trailing spaces like /orders/ or without it /orders to avoid any confusion.
- Use sorting, filtering, querying, pagination: In many use cases, a simple resource name won’t work.
- Sorting: You need to request server API resources to sort data in ascending or descending order: GET /orders?sort=asc
- Filtering: Filter on some business conditions like return product catalog responses based on price range: GET /orders?minprice=100&maxprice=500
- Querying: Use cases where you want to query products based on their category like searching electronics products based on mobile category, for example: GET /orders?ctg=mobile&userid=111
- Pagination: To improve performance and reduce latency on API calls over the internet, the client requests a subset of records at a single request like 10 records at a time for a given page. It’s called pagination: GET /orders?page=1&page_size=10
- Versioning: Versioning is a very important concept of API, which helps consumers to migrate to newer versions without any outage. In this scenario, some clients can access newer versions, and others can still use older versions. There are various ways of API versioning:
- Using URI path: It’s a standard technique to maintain different versions of the same APIs to support older versions of API resources, if the server-side API resource is upgraded to a newer version. Clients take some time to migrate and use the latest version of API. A new version to the same API resource can be changed, for example, /order/v1, / order/v2. The internal version of the API uses the 1.2.3 format like this: MAJOR.MINOR.PATCH.
- Major version: It contains major code changes in business logic or other components. A new major version is added to the new API and the version number is used to route to the correct host.
- Minor and patch versions: These are used internally for backwardcompatible updates. They are usually communicated in changelogs to inform clients about new functionality or a bug fix. The minor version represents minor changes and the patch contains break-fixes or security patches, and so on.
- Using query parameters: In this method, version number is added into query parameters in key and value. It’s simple to use, however not recommended because it’s difficult to route requests to APIs. For example: /orders?version=1.
- Using custom headers: In this method, version number can be added in HTTP request header. It avoids the clutter of URI versions; however, we need to create and manage new headers. For example: Accepts-version: 1.0.
- Using content negotiation: It’s also added in the header, allows a single resource representation instead of versioning the entire API which gives us more granular control over versioning. In this method, no need to create routing rules at the source API codes of different versions. This approach is not very popular, because it’s difficult to test and verify changes in browsers. For example: Accept: application/json; version=1
- Caching: API caching is a very much needed feature to improve API read (GET) performance. It caches responses from the API and for the same set of data and makes it available for other similar client requests. It’s recommended to use distributed caching techniques in a distributed microservices environment so that the same cached response should be available for multiple instances of the same microservice app.
- Rate limiting and throttling: Rate limiting is a technique of counting client requests with counter and limit based on the subscription or maximumallowed limit to control traffic on the server, also it is useful for security reasons to avoid hackers to hit continuously and bring the system down by consuming all the memory and compute resources.
API throttling controls the way API is being consumed by external apps/ or clients. It also indicates a temporary state and is used to control the data that external clients can access through a REST API. When a throttle is triggered, we can disconnect client requests, client apps, device ID, a user or just reduce the response rate. You can define a throttle at the application, API or user level.
There are multiple ways to implement rate-limiting. Spring Cloud Gateway provides rate limiting wrapper using distributed caching such as Redis or a similar caching tool.
- API gateway support: It’s recommended to expose APIs using API gateway tools to external apps. API gateway takes care of routing and orchestrating to designated server-side API, filtering, rate limiting, throttling, circuit breaker, API, authentication, authorization, and so on out of the box. It makes your API configuration outside of the business logic source code. It makes actual business logic code lighter and easy to debug and maintain.