Good APIs speak for themselves. Often times when I am going through a documentation, I have a tendency to skip all the way up to directly seeing the code, and I am pretty sure I am not the only one who does this. Interestingly almost in all the cases I am able to derive the 90% of information from the code itself. I have never been a fan of explaining ‘what’ a piece of code does, documentation is needed for stuff that is not communicated through a 30 sec look at code.
Let us take an example of an API I designed recently, it was an API to create sidebar in our application. This API is little tricky to design because sidebar is a feature which can be customized heavily. This is the API description we came up with:
<SidebarView baseItems: SidebarItem[] >
abstract class SidebarItem {
abstract id: string;
abstract name: string;
abstract actions: Action[]
abstract getChildren (): Promise<SidebarItem[]>
}
Let us analyze this api for few key parameters that any good API should have:
API inputs should resonate with need of the consumer
Parameter names should be self explanatory
Your api input should do/control one thing
API inputs should resonate with need of the consumer
Every api requires some user input to function. This input should resonate with the need of the consumer. For instance in our sidebar api, we had these choices when we were designing this api:
// choice 1
<SidebarView pseudoBaseItem: SidebarItem >
// choice 3
<SidebarView baseItems: SidebarItem[] >
We decided to go with choice 3, let me explain our reasoning:
As sidebar is usually a hierarchical list of items, each of our sidebarItem has a getChildren property. To initialize the sidebar, we could have taken a input of pseudoBaseItem which would have been a pseudo item in a sense that it would not be displayed, but instead will be used for calling getChildren and rendering the children of that item. This is not a good design, the parameter pseudoBaseItem is not something that strikes as natural requirement of sidebar, it is not clear from the name what is the intended usage of this item. So instead of that we went with the more explicit baseItems property which is more clear in its intent.
API should be declarative in nature
Declarative vs imperative are the two major paradigm that you need to choose between whenever designing your APIs. In imperative paradigm you give more freedom to your consumers.
Coming to the sidebar api analogy, let us say you want to add support to drag drop your sidebar items. Also your sidebar items can only be dropped on certain locations, for example your ItemX can be dropped to a ItemY but not visa-versa.
There were two options for us to design this api:
// choice 1
class ItemX extends SidebarItem {
canDropItems (items: SidebarItem[]): boolean {
return items.every((item) => item instanceof ItemY)
}
}
// choice 2
class ItemX extends SidebarItem {
allowedDropTargets = [ItemY]
}
Both the choices achieve the same thing, they help sidebar take the call whether the drop is allowed to happen at the place at which user is trying to drop the items. Choice 1 is imperative, it gives more freedom to consumer, as now consumer can implement a custom logic in canDropItems , but at the same time it introduces more chances of possibility of error and code duplication. We went with choice 2, it not only eases the job of consumer, but also eradicate tons of potential bugs. Remember the fastest way to solve a bug is to not have it in the first place.
Your api input should do/control one thing
Let us take a detour into one of our favourite tool of all time, git introduced git switch and git restore in v2.23, here is the commit message:
"git checkout" doing too many things is a source of confusion for many users
(and it even bites old timers sometimes). To remedy that, the command will be
split into two new ones: switch and restore.
Taking example from our sidebar api, we earlier introduced a parameter called isEditable in our api, this parameter was being used at multiple places where we needed to make a permission check. But precisely because it was being used at many places with many different scenarios we removed it and introduces several focused parameters like: isDeletable, isDropAllowed.
Top comments (0)