- API Design Tutorial
- API Design: Lots of Upfront Design
- API Design: Keep it Small and Focused
- API Design: Don't Expose More than Necessary
- API Design: Provide Sensible Defaults
- API Design: Optional Abstractions
- API Design: Central Point of Access
- API Design: Don't Force the User to Assemble Components
- API Design: Avoid External Dependencies
- API Design: Avoid Logging in your APIs
- API Design: Design for Testing
- API Design: Design for Easy Configuration
API Design: Optional Abstractions
A technique I often use to make API's more flexible is a technique I call "Optional Abstractions". In this text I will explain what optional abstractions is, and how you (or your API users actually) can benefit from optional abstractions.
Software as Layers
Software is often layered in multiple layers ontop of each other, each layer calling down trough the layer below to obtain some service. Each layer is an abstraction which makes the layers below easier to work with for the layer above. Your API can be thought of as one of these layers. Here is a basic illustration of the layers present in a Java Application (it would be similar for a .NET application):
One of the main concepts of layered software is that each layer only talks to the layer directly below it. In other words, each layer is an abstraction of the layers below it.
API's as Layers
In the illustration of the layered software model displayed earlier the Java API's are displayed as just a single layer. However, an API can itself consist of several layers. For instance, my Java persistence API Butterfly Persistence consists of several layers internally. Here is how these layers look:
The lowest layer in Butterfly Persistence is the JDBC database connection. Ontop of that are a set
of JDBC utilities making JDBC easier to use. These are utilities for opening and closing
ResultSet's. At the top are two different layers. One makes it
possible to read and write objects. The other one makes it possible to read a
ResultSet into a
Map, which is useful if displaying data from dynamic queries.
As mentioned earlier, each layer in a layered software model is only supposed to communicate with the layer just below, and just above itself. However, what often makes an API really flexible is the ability to bypass a layer and communicate directly with the lower layers. In other words, that the layers (abstractions) are optional. This is important to make sure that an abstraction (layer) does not "get in the users way", as mentioned in my definition of functional software elegance in the introduction.
For instance, if you do not need to read or write an object,
nor read a
Map, in Butterfly Persistence you can access the JDBC utilities directly instead,
to do what you need to do. In fact, you can also bypass the JDBC utilities and work directly on the
database connection if you need to. You can even combine the layers within the same transaction.
For instance, you can open a
ResultSet and iterate it using the JDBC utilities, and
then have the object reading layer read objects from records in the
how optional the layers in Butterfly Persistence are.
In addition, Butterfly Persistence is able to guess object-to-table mappings automatically. In other words, you just tell Butterfly Persistence what class you want to read an object of, and Butterfly Persistence will automatically guess what table those objects come from, and which columns matches which getters / setters, find the primary key etc. The goal was to make the API easier to use for the average user.
However, sometimes the names used in the classes and the names used in the database are so different that automatic mapping is not possible. To avoid "getting in the users way" as stated in my definition of functional software elegance in the introduction, I had to add a manual mapping mechanism. A mechanism where users could explicitly specify what table a given class matches, and what column a getter / setter matches. In other words, I had to allow the flexibility of bypassing the automatic mapping. In fact, I made it possible to combine automatic and manual mapping, for increased ease of use and flexibility.
A third example is Butterfly DI Container, a dependency injection container I have designed. This too was designed for extreme flexibility. For instance, at any time if a certain object configuration gets too complicated to configure using the Butterfly Container Script, you can just write that as Java code in a static method (or normal method even). Then you can call this method from your script. This is an easy way of bypassing any limitations in the script language.