dagblog.
← Archive

A little about API design


Last night I was still propping my eyes open with matchsticks as I wrote up a few observations for my code review presentation on Monday. I also finally finished reading an interview with Jarda Tulach about the release of his book Practical API Design, subtitled Confessions of a Java Framework Architect. I should say up front that the interview made me feel this book belongs on my shelf. It’s also time to reconsider my somewhat skeptical view of code review, but let’s take things in order, because it all started somewhere else.

We built an API whose purpose doesn’t matter to this story. You need Spring to start using it. We originally assumed that anyone using the API, meaning a client, would ideally be managed by Spring themselves. With IoC, everything would fit together like pieces of a jigsaw puzzle. One of the main classes in the API was a factory for domain objects such as apples, pears, plums, and peaches. This factory also had a method for creating unique identifiers for those domain objects. All our objects were represented externally by interfaces, including the identifier.

public interface OvocnaTovarna {
public T newOvoce(Class typOvoce);
}
public interface IdTovarna {
public Id newId();
public Id fromString(String idAsString);
}
The difference between the Ovoce and Id implementations was that Ovoce really did represent a nontrivial structure. To create an instance, you needed to know some context. The identifier, on the other hand, was a “simple” object that needed no context to construct. For consistency in the API, though, we decided that Id would also be created by a factory and represented by an interface, like the other domain objects. Another reason to make Id an interface was that we could change its implementation if, for example, the underlying java.util.UUID didn’t meet our needs.
Yesterday, when I reviewed the code of components that use our API, it turned out that this design had hit a snag with clients, leading to some interesting and often repeated paradoxes. A client managed by Spring did use the fruit factory, yet instead of using the identifier factory, it stubbornly called the constructor of the only available implementation of Id.
The code looked like this:
public class FooComponent { 
@Autowired //zajisti dependency injection implementace
private OvocnaTovarna ovocnaTovarna;

public void doSomethingFishy(String idAsString){
IdImplementace id = new IdImplementace(idAsString);
Hruska hruska = ovocnaTovarna.newOvoce(Hruska.class);
...
}
}
… even though the use we’d intended was right there.
public class FooComponent { 
@Autowired
private OvocnaTovarna ovocnaTovarna;
@Autowired
private IdTovarna idTovarna;

public void doSomethingFishy(String idAsString){
Id id = idTovarna.fromString(idAsString);
Hruska hruska = ovocnaTovarna.newOvoce(Hruska.class);
...
}
}
In the interview I mentioned, Jarda Tulach gives two basic axioms.
  • “The first version of an API is never perfect.”
  • “You cannot know all the users of your library.”
The first axiom was borne out almost completely. From the code review we know that users use some parts of the API differently than we intended. The question is why. Most likely, we didn’t document well enough that users should create identifiers with the factory. It’s less likely, though still possible, that they just didn’t want to use factories for “simple” objects. I’d understand that more readily where Spring is used programmatically, with programmatic bean lookup. And, to be honest, nobody likes a factory for factories.
The second axiom is far more dangerous for us. Someone can use the API whose code we’ll have a hard time reviewing, because we don’t know everyone who has used the API or where they used it. We can learn from this and do three things next time:
  • Document the API well, including a usage example
  • Be defensive. The implementation class can be kept out of the public API.
  • Be more pragmatic. We could have chosen a class for the identifier in place of the interface.

Medium version

Related articles

Related CZ Podcast episodes