API Client Library (SDK) Design Guidelines

Motivation

Perhaps the words are too generic, or the field too limited... In either case, I've not been able to find resources that describe best practices for creating code libraries, aka SDKs, against published APIs.

In response, I'm writing this, so that maybe others can find something, contribute ideas or constructive criticism, so we can collectively improve.

Design Goals

I've created a few client libraries while at Twitter (Enterprise data) and am currently working on a new one for Sprinklr's published APIs. As I was embarking on this new library, I wanted to put some thought into it's design.  

"What makes a good client library?"
In my opinion, the two primary goals of a library should be to improve the developer experience, speed integration with the underlying API, and provide reliable access to the end-user/system.

At a minimum, it should:
  • Hide the mechanics of interacting with an API
  • Enable using existing API documentation for parameters & returned data
  • Be consistent with the norms of the implementation language
Improving the developer experience comes down to reducing the complexity of using the API. Expanding the client library past one for one calls and creating a more robust library of code that further reduces the need for a developer to rely on API documentation can make sense depending on the complexity of the underlying API.

Implementation Goals & Decisions

In the process of making a client library for an API, there are several design decisions that are essentially made on behalf of the developer. Since these decisions will affect them, it is best to know what they are in advance, and document what and why they were made. These can be documented in a design doc and shared with developers as part of the library.

Will the naming convention be consistent across languages or language natural?
Different languages have different style guidelines. For example, for an underlying API call to retrieve user information, a method in Python may be get_user, but in NodeJS it would be getUser and in C# GetUser.

To what level will methods, parameters and return values be documented?
Ideally it's to the extent possible to allow for IDE environments to expose useful information to developers, consistent with either the API itself or enrichments made to the library.

How will the client library source code be documented? 
Should it provide the developer guidance on any design choices made that diverge from the API standard, and/or links to underlying API documentation? Will it be open sourced?

How will error states be communicated to the developer/application?
For the Sprinklr library, I made the decision that the success of the call was the only return value of the methods. Any returned data or error messages are communicated via the client object. Thus, when using the library, any calls can be reliably patterned as:
if client_library_call(params):
   # process call data
else:
   # process error

Will the library hide API consistencies or expose them?
No API is perfect, and one of the benefits of using a client library is that it can hide some of the inconsistencies of a published API. These usually expose themselves as return values or error messages that vary in type or formatting, or in methods of paging through data. By hiding these inconsistencies with a consistent client library interface, the developer experience can be improved.

What external libraries will be needed?
Reliance on as few external libraries as necessary is ideal, as it reduces the risk surface area. Risk comes from the potential changes to the external libraries that may break the client, or inject vulnerabilities into an application developed using it.

Maturity Level Library Design

Chances are developers will be happy with anything that helps them get started. Don't wait until it is perfect to start publishing it! Consider this model of maturity levels for major releases:

Level 1

  • Basic class structure to store authentication requirements needed for individual API calls
  • Direct API access - using language specific method and variable naming standards, but as consistent to the API endpoint names as possible
  • Error 'capturing' - preventing API calls from surfacing errors directly to the application while providing access to detailed error information
  • Consolidated API requests (GET/PUT, etc) for efficiency

Level 2

  • Additional methods that call Tier 1 endpoints with parameters with the goal of providing consistent return objects
  • Validation of requests (prevent API errors or provide more robust feedback)
  • Logging of requests, responses & errors

Level 3

  • Classes to facilitate requests or response parameters
  • Constants used for requests or response parameters

Level 4

  • Serialization of complex requests to enable SDK agnostic storage / templating capabilities

I'll be updating this article with more information over time, and ideally with your input! Please leave a comment with thoughts, ideas and questions!

Thanks!
Dz

Comments

Post a Comment