API Design and Related Considerations
As APIs form a greater part of product development, there needs to be an accompanying design strategy. At a lower level, and not a consideration here, are needs for an API to be reliable, fast and available. Also not discussed are:
- API URI structural conventions/preferences
- security
- load management
- bulk data e.g. pagination
- response code handling consistency
- API response management
It may start to become clear, in some respect distressingly so. that the level of consideration required for API management as a whole, is complex. A narrow part of API management relates to design considerations, and listed below are some long-term options that are covered for URI standardisation:
- versioned APIs
- parameter controlled APIs
- API granularity
- web server usage
- client API documentation
Versioned APIs
Using version numbers to represent APIs is not uncommon, and this can be a combination of major and minor, or just major e.g.
/v1.0/all/rest/uris /v1/all/rest/uris
It would be only be necessary to update an API version if the changes will break existing client handling. New URIs, optional parameters, or any other non-breaking updates do not mandate any version change. A change in the major version is warranted if there are arbitrarily significant changes e.g. the whole API.
It's also possible to map /v1.1 to /latest, either using a web server e.g. Apache, or within the application i.e. version v1.1 is synonymous with latest.
A version does not necessarily need to apply to the complete API; an alternative option is to apply it to grouped API URIs e.g.
/v1.0/users/all /v1.1/users/all /v1.0/user/id /v1.1/user/id /v1.0/customers/all /v1.0/customer/id
The above suggests that the API is grouped around user and customer. This, potentially, limits the level of change for the client e.g. the particular API group is not used by the client, limits the update requirement. From a development/deployment point of view, it also means there is incremental upgrade to a complete API set, limiting issues to a narrow set of the API.
Yet another option is to version each individual endpoint, and manage each endpoint in its own right.
/users/v1.0/all /users/v1.1/all /user/v1.0/id /user/v1.1/id /user/v1.2/id /customers/v1.0/all /customer/v1.0/id
This level of granular versioning can have significant advantages for development and client. as it provides a level of flexibility and URI isolation to build data as required. For example, some clients may prefer to use v1.0 to get all users and v1.2 for a specific user. Taking that one step further, a paying client may require some new updates for a particular endpoint. This versioning strategy would not impact any other client, does not affect any other dependent API endpoint, as well as being made available to other clients.
Of course, any of the above options implicitly requires a supporting, and robust, development strategy. For example, using Java, each endpoint could have its own class and that class contains all versions of that endpoint, or packages based on version number.
It is also necessary to have accurate documentation that is easy to use in terms of hierarchy, search and detail. Although client-facing documentation is an important consideration in its own right, it becomes increasingly so as the level of versioned-API reaches a per-endpoint basis.
Parameter controlled APIs
If versioning is not a viable option, then parameter control maybe. In much the same way as commandline tools have various flags to control operation, an endpoint can handle multiple request parameters that control processing and result. For example,
- max-result parameter controls the number of returned results
- data-result can be used to manage the data structure returned, such as list of fields to return for each result, or use a data wrapper for all results
- id-error that determines if a list of ids to get customers should fail if 1 or some or all the ids are invalid
- deep-data parameter to ask for detailed data response or skeleton data
In this way new parameter types, or values for existing parameters, can be used to provide updated functionality, and there is only ever one version of the API, and its functionality is micro-managed using parameter values. Naturally, there will always be implicit default values where applicable.
From a development perspective, and considering Java again, it might be cleaner to have a endpoint-to-class approach. There maybe considerable processing, specific to an endpoint and it would become too unwieldy to have large classes e.g. conditional processing for multiple endpoints, large unit test classes.
API granularity
Let's use the users/all as the serving example. This endpoint might return just a list of user ids, or possibly user id, and name for display purposes. The next step is to get user data with the user/id endpoint. The user data may have a list of user affiliations represented by a list of affiliation ids/names. The endpoint affiliation/id will give data for a specific affiliation. You get the picture... In this way the URIs are kept short and succinct, and easy to manage as grouped APIs.
No endpoint ever returns a huge mass of data to process, and each request is a part of a data-mining exercise. The volume of response data is where some thought is required i.e. how much response data is necessary for that endpoint to be considered useful. Each endpoint has to provide enough data for a client to work with, and support required functionality.
Naturally, this increases the number of requests to achieve required data, with knock-on effects such as increased load on the service, and greater need in managing response times.
Web server Usage
Having a web server e.g. Apache, Nginx sit in front of an application-based API service, makes for a flexible combination. In fact, my preference is to always have a web server present. It is possible to manage URL redirects and rewrites to some benefits e.g. a service can rewrite all its URI structures without affecting clients. It also gives clients time to update with new changes, without any disruption.
Files, such as images, can be cached at the web server level, generic headers added to all responses, manage handling based on user-agent or header request data. Some web servers can also be used to manage request throttling where appropriate, or even block specific IPs altogether. It's also possible to use the web server to return appropriate response codes, for requests that are expired/redundant, use non-existent urls, and so much more All this would happen without the request hitting the application server.
In fact, Apache should be considered as a development domain in its own right alongside API development. Various cloud-based providers may have their own variations or derivative of a standard web server version.
It's also possible to introduce an API Gateway e.g. KrakenD, Kong, Tyk to manage all hosted service APIs. There are a variety of gateway options available and Kong is certainly very popular. It maybe worth noting that Kong runs on Nginx and is open source. KrakenD has many attractive features and is also open source.
Client API documentation
Having a clear, concise, accurate documentation source is critical for client usage, as well as being easy to navigate and search. In some respects it has to mimic the actual API site e.g. availability, page response times. There needs to be a dedicated source to keep this up to date, or at least a defined process.
The documentation should explain how to start using the API, from gaining access to endpoint usage i.e. end-to-end documentation. As well as information on how to create a trial account, there will be,
- licensing information
- a contact point
- reporting issues
- submitting change/update requests
Final Comments...
These are only a few of the considerations for an API and it's always a good approach to consider long term solutions, such as design. It is certainly not advisable to make no attempt at any design aspect, or make bad design decisions e.g. attempt to future-proof without any design strategy.