I have designed a few API’s in my days, but during my work with Bowler, a few things occurred to me: creating API’s for API consumers vs. API’s for others to implement are slightly different beasts, not by much, but there’s still a difference.
You generally find two types of API’s:
- Those that people will simply use, but not really extend - API’s that are consumed in a simple manner.
- API’s that are meant to be implemented, but not necessarily consumed by the same developers. This would typically be things like pluggable behavior in a wider framework.
In the first case, you want to make the API simple and straightforward to use, an example of this would for instance be the Renderable, Validations and ParameterMapper traits of the Bowler API as shown in the code below:
These API’s are there to make life easy for application developers, and are simply “consumed”/used, but I don’t foresee a lot of cases where a developer would actually want to or need to extend them in any way.
The flipside of this is API’s that are meant to be implemented, but rarely seen by the application developer. In Bowler, an example of this is the RenderStrategy trait and the ViewRenderer that it returns: A RenderStrategy in Bowler is responsible for inspecting a Request and chosing a ViewRenderer (something that knows how to render a response) based on the Requests characteristics, such as for example the “Accept” HTTP Headers. A RenderStrategy and ViewRenderers are easily pluggable and configurable behaviors that most developers will likely never need- or want to touch. But the option for customization is there - yet once the customization is there, it is generally not seen again unless there is an issue related to it.
Ease of Use vs. Ease of Implementation
One very clear example of the distinction between ease of use for API’s for consumption vs. ease of implementation is the previously mentioned Renderable trait: Renderable’s “render” function takes a vararg argument of the Resource View Model an application developer would want to render. Varargs can be great and make things very easy to use from an application developes point of view.
However, varargs are hellish and fraught with complications for someone implementing based on an API, hence varargs do not turn up in any Bowler traits which are primarily mean to be implemented, it is only used in Renderable for ease of use for application developers.
What I wanted to illustrate with these examples is simply the fact that as a API designers, we have to consider the use case for an API, will people simply “consume” it, or will they primarily implement it? This fundamental question may and perhaps should impact your design in subtle ways which may have a larger impact down the road than you ever thought.