Building an API Client
Over the last few years I've found myself writing and implementing a number of client libraries for different APIs. PHP to scrape dailymile and twitter, PHP to post updates to twitter, and even more PHP to interact with customer.io. Okay, so maybe most of my API building and consuming experience has been with PHP.
Anyways, with the amount of discussion that proper API structure has triggered (with RESTful endpoints, proper failure headers, partial content, etc) I find it interesting that few people have focused on the client end. Is there a list of best practices for building a reliable API client in a given language? When I started building a PHP interface for Bigstock's API w/ OAuth2 I tried to build it with what I feel could be good general guidelines for client libraries.
Don't hide endpoints or parameters
This one may be a bit controversial. After all, hiding endpoints and mapping parameters inside the client library is a great way to decouple the main core from the API. If anything on the API changes then one only has to update the client. However, there is one serious drawback.
Most APIs have excellent documentation (though not all do). They will list out the endpoints, acceptable parameter values, and (sometimes) link out similar calls if you need to make several to reach the data you're looking for. When a developer is looking to integrate they can go straight for this documentation to see what the limits and possibilities are.
So why hide it? After they read up on the endpoints and figure out which ones they want to use why make a user relearn the calls? This will only increase the integration time and make debugging more difficult. Once they figure out that they want to pull statuses/mentions_timeline.json to get to that twitter data why make them learn to use $twitter->getMentionStatuses() from a client?
Don't validate parameters before sending
This one follows the same tone as hiding endpoints. Any decent API will return with a well-formed error response if there are missing or incorrect parameters sent. Why should a client replicate all of that validation logic? All it does is increase the chance that some of the validation will fall out of sync and reject proper parameters (or allow improper ones) for a call.
The one reason why a client may want to validate parameters pre-send is if latency is a factor. However, I'd argue that the risk of client validation being incorrect outweighs the benefit of this performance gain.
Don't parse the response into custom formats
There are a lot of reasons why a user may want to communicate with an API. They may be posting information or fetching it, and afterwards they may want to spill information straight to the screen or save the results in a table. There is no good reason for the overhead of building custom objects. If an API will return a json string then the client should return a json object. If an API simply returns a string then the client should simply return a string. This leads into the next point…
Do only encapsulate language-specific logic
The point of a simple client is to encapsulate language-specific logic needed to communicate with the API. For Bigstock's API I needed to setup a custom curl request with headers and a post body. Because its OAuth. So the client should identify the type of request and map that to a specific curl package. A user shouldn't have to worry about customizing the options passed into the request. All they want is the response based off of an endpoint and parameters.
If a client is doing more, like wrapping endpoints or validating parameters or parsing the response to return different objects, then it's more than a client. Which isn't a bad thing. It just means that the client has more responsibility and more potential points of headache.
Do allow a few shortcuts (maybe)
Just because a client should be simple doesn't mean it can't give the user a few shortcuts. With OAuth2 there is the idea of tokens. You need to make an initial request with the client id and secret to fetch a token. Once you have the token you can make other endpoint calls. I didn't want to make the user figure all this in, as this API flow is a bit special to this case. So I gave them a shortcut.
...
class Client
{
...
public function request($endpoint, $parameters = array())
{
$is_token_request = ($endpoint == self::TOKEN_ENDPOINT);
if (empty($this->token) && !$is_token_request) {
$this->token = $this->fetchToken();
}
...
}
...
}
If the user attempts to make a request and a token has not been defined in this instance (it can be passed in manually if the user wants to store the token in their system, be it session or otherwise) then the client will happily fetch it manually before the normal request. So the shorthand implementation would be…
$client = new Bigstock\OAuth2API\Client(false);
$client->setClientCredentials(CLIENT_ID, CLIENT_SECRET);
$response = $client->request('search', array('q' => 'upper peninsula'));
And the longer alternative is…
$client = new Bigstock\OAuth2API\Client(false);
$client->setClientCredentials(CLIENT_ID, CLIENT_SECRET);
$response = $client->request('token');
if (isset($response) && isset($reponse->access_token)) {
$token = $response->access_token;
// save the token if so desired
$client->setToken($token);
$response = $client->request('search', array('q' => 'upper peninsula'));
}
// handle the error here
I'll be adding some more shortcuts to this client down the road (since there are some other interesting hashings that still need to be done with the Bigstock API). However, all of the normal endpoints work just fine. Also, I plan on using this work to model future client builds (including a long-forgotten twitter client collaboration with @dave_kz) unless some better practices come down the road.
Comments (0)