User-driven, Pre-emptive APIs
Aneel makes an argument for "tools vs. methods". To summarize, should tools enforce the methodology or methodology the tool.
Tools need to move when we do. And they need to be made to be moved by us. But, not in a vacuum. The idea of user-driven innovation should be built into professional tools. In organizations where policy and methodology comes down from on high, when a method-enforcing tool is modified by an end-user (hi!), the bigwigs should go: “huh, Bob is doing this differently.. wonder if he’s onto something?”. Then someone should go find out if he is.
Source: tools vs. methods
IBM Rational has the concept of patterns built into the tool. Internally, IBM has contests (accompanied with decent rewards) for developing patterns for Rational.
The same argument can be made for APIs in general. Can APIs be designed in such a flexible way that when a use case changes, so can the result when the API is invoked? Can we ship with n patterns and best practices but leave the API wide open for anybody to plug in their own functionality?
You bet we can. And then, falling back on Aneel's argument, once the bigwigs see that users are creating patterns, perhaps include it into the API as convenience methods.
Pluggable Data API
For instance, I'm working on a data access API that wraps JDBC access and ships with IBM's pureQuery. The main goals of the API is to enable clearer persistence code by using code to describe what is being done instead of how it is being done. Second, drastically reduce the boilerplate code associated with JDBC. Third, use collections and beans rather than ResultSet. Fourth, do not be a full-featured object relational mapper.
Let's look at some of the methods available. The variable "data" in these examples are an instance of the "Data" interface, which defines these methods, and assumes it has been constructor injected with a DataSource.
First, you can get a Map out of a table row:
Map<String,Object> brian = data.queryFirst("SELECT * FROM person WHERE person.name=?", "Brian");
If you want to get at many rows:
List<Map<String,Object>> people = data.queryList("SELECT * FROM person WHERE person.name LIKE ?", "Br%");
Second, you can get a POJO bean out of a table row:
Person brian = data.queryFirst("SELECT * FROM person WHERE person.name=?", Person.class, "Brian");
Likewise, beans as from many rows:
List<Person> people = data.queryList("SELECT * FROM person", Person.class);
But what if you want to get a the result(s) of a single column, say from an aggregate function, for instance, you could retrieve a String by:
String name = data.queryFirst("SELECT name FROM person WHERE person.name=?", new ScalarRowFactory<String>(String.class),"Brandon");
What's with the "ScalarFowFactory", you ask? Well, I'm glad you did. This is the pluggable part, where user-driven extendibility makes all the difference. It would be naive to think that an API could be created that would meet all needs for all people. In fact, I often tell my collegues I work with "If you try to create for all, you provide value for none". That's worth repeating...
If you try to create for all, you provide for none.
This is the problem with JEE. It tries to boil the ocean and be the end all framework. Then we get lighter weight contenders like Spring and Co. Granted, with 1.4, we see reactive APIs for those uncomfortable with the heavyweight requirement imposed by JEE. Others see an easier method and create APIs to meet the need. Now JEE is adapting.
So this is all about creating pre-emptive APIs. What is a pre-emptive API? It is an API that leaves open doors for developers to customize the programming experience. Yes, and that means allowing them to shoot themselves in the foot, too. Sure, we can provide convenience methods on top in the API itself, but it should not play a Microsoft and have special privileges that the user-driven portion does not.
Would JEE be different had it been built with a pre-emptive APIs?
So, in the above example, the Map and bean examples simply drive through the same methods that the String example did. They are simply helper methods.
The API, however, is wide open. Methods can be of type queryFirst, queryList, queryIterator, or just plain query. queryFirst deals with one result. queryList deals with a collection of results and pre-processes results. queryIterator is like queryList but provides a lazy retrival of results on demand in user code and is more efficient in some situations.