26

When discussing APIs between systems (on the business level) there are often two different point of views in our team: some people prefer a more - lets say - generic abstract approach, other a straight forward "concrete" approach.

Example: the design of a simple "person search" API. the concrete version would be

 searchPerson(String name, boolean soundEx,
              String firstName, boolean soundEx,
              String dateOfBirth)

People in favour of the concrete version say:

  • the API is self-documenting
  • it is easy to understand
  • it is easy to validate (compiler or as a webservice: schema validation)
  • KISS

The other group of people in our team would say "That is just a list of search criteria"

searchPerson(List<SearchCriteria> criteria)

with

SearchCritera {
  String parameter,
  String value,
  Map<String, String> options
}

with possibly making "parameter" of some enumeration type.

Proponents say:

  • without changing the (declaration of) the API the implementation can change, e.g. adding more criteria or more options. Even without synchronizing such a change at deployment time.
  • documentation is necessary even with the concrete variant
  • schema validation is overrated, often you have to validate further, schema cannot handle all cases
  • we already have a similiar API with another system - reuse

The counter-argument is

  • lots of documentation about valid parameters and valid parameter combinations
  • more communication effort because it's more difficult to understand for other teams

Are there any best practices? Literature?

6
  • 3
    repeated "String first/name, boolean soundEx" is a clear violation of dry and suggests that this design failed to address the fact that name is expected to go along with soundEx. Faced with simple design mistakes like that, it feels hard to proceed with more sophisticated analysis
    – gnat
    Commented Sep 11, 2013 at 21:32
  • The opposite of "concrete" isn't "generic", it's "abstract". Abstraction is very important for a library or API, and this discussion is failing to ask the truly fundamental question, fixating instead on what is quite frankly a rather trivial question of style. FWIW, the counter-arguments for option B sound like a load of FUD, you shouldn't need any extra documentation or communication if the API design is even half-clean and follows SOLID principles.
    – Aaronaught
    Commented Sep 12, 2013 at 2:04
  • @Aaronaught thanks for pointing out that out ("abstract"). It might be a translation issue, "generisch" in German sounds still OK to me. What is the "truly fundamental question" to you?
    – erik
    Commented Sep 12, 2013 at 7:05
  • 4
    @Aaronaught: The question is not about abstract. The right correction would be that opposite of "generic" is "specific", not "concrete".
    – Jan Hudec
    Commented Sep 12, 2013 at 7:10
  • Another vote for this not being about generic versus abstract, but generic versus specific. The "concrete" example above is actually specific to the name, firstName and dateOfBirth, the other example is generic to any parameters. Neither are particularly abstract. I'd edit the title but I don't want to start an edit war :-) Commented Sep 12, 2013 at 8:29

10 Answers 10

19

It depends on how many fields you're talking about, and how they are used. Concrete is preferable for highly structured queries with only a few fields, but if the querying tends to be very free form, then the concrete approach quickly becomes unwieldy with more than three or four fields.

On the other hand, it's very difficult to keep a generic API pure. If you do a simple name search in a lot of places, eventually someone is going to tire of repeating the same five lines of code, and they're going to wrap it in a function. Such an API invariably evolves into a hybrid of a generic query together with concrete wrappers for the most commonly used queries. And I don't see anything wrong with that. It gives you the best of both worlds.

7

Designing good API is an art. Good API is appreciated even after time passes. In my opinion, there should be no general bias on the abstract-concrete line. Some parameters can be as concrete as days of the week, some require to be designed for extensibility (and it is quite stupid to make them concrete, eg, part of function names), yet another may go even further and in order to have elegant API one needs to provide callbacks or even domain specific language will help to fight complexity.

There are rarely new things happening under the Moon. Take a look at the prior art, especially established standards and formats (e.g., many things can be modelled after feeds, event descriptions were elaborated in ical/vcal). Make your API easily additive, where frequent and omnipresent entities are concrete and envisioned extensions are dictionaries. There are also some well-established patterns for dealing with specific situations. For example, handling HTTP request (and similar) may be modelled in the API with Request and Response objects.

Before designing API, brainstorm on aspects, including those, which will not be included, but you must be aware of. Examples of such are language, direction of writing, encoding, locale, timezone information and the like. Pay attention to places where multiples can appear: use list, not single value for them. For example, if you are desing API for videochat system, your API will be much more useful, if you assume N participants, not just two (even though your specs at the moment are such).

Sometimes, being abstract helps to reduce complexity drastically: even if you design a calculator for adding only 3+4, 2+2, and 7+6, it may be much simpler to implement X+Y (with technically feasible bounds on X and Y, and include ADD(X, Y) to your API instead of ADD_3_4(), ADD_2_2(), ...

All in all, choosing one way or another is just a technical detail. Your documentation should describe frequent use cases in a concrete manner.

Whatever you do on the data structure side, provide a field for an API version.

To summarize, API should minimize complexity when dealing with your software. TO appreciate the API, the level of exposed complexity should be adequate. Deciding on the form of the API depend a lot on the stability of the problem domain. Thus, there should be some estimation on which direction the software and it's API will grow, because this information may affect the equation for complexity. Also, API desing is there for people to understand. If there are any good traditions in the software technology area you are in, try not to deviate much from them, as it will help understanding. Take into account for whom you write. More advanced users will appreciate generality and flexibility, while those with less experience may be more comfortable with concretics. However, care for the majority of API users there, ie those in between beginners and experts.

On the literature side I may recommend "Beautiful Code" Leading Programmers Explain How They Think By Andy Oram, Greg Wilson, as I think beauty is about perceiving hidden optimality (and suitability for some purpose).

1

My personal preference is to be abstract, yet my company's policies lock me into being concrete. That's the end of the debate for me :)

You've done a good job listing pros and cons for both approaches, and if you keep digging you'll find plenty arguments in favor of both sides. As long as your API's architecture is developed properly - meaning you've given thought as to how it will be used today and how it may evolve and grow in the future - then you should be fine either way.

Here's two bookmarks I had with opposing viewpoints:

Favoring Abstract Classes

Favoring Interfaces

Ask yourself: "Does the API fulfill my business requirements? Do I have well defined criteria for success? Can it scale?". Those seem like really simple best practices to follow, but honestly they're far more important than concrete vs. generic.

1

I wouldn't say that an abstract API is necessarily more difficult to validate. If the criteria parameters are simple enough and have little dependencies between each other, it doesn't make much difference whether you pass the parameters separately or in an array. You still need to validate them all. But that depends on the design of the criteria parameters and the objects themselves.

If the API is complex enough, having concrete methods is not an option. At some point you'll probably end up with either methods with to much parameters or too much simple methods that won't cover all of the required use cases. As far as my personal experience in designing an consuming APIs, it's better to have more generic methods on the API level and implementing specific required wrappers on the application level.

1

The change argument should be dismissed with YAGNI. Basically unless you actually have at least 3 different use-cases that use the generic API differently, chances are pretty low you design it so that it won't have to change when next use-case comes up (and when you have the use-cases, you obviously need the generic interface, period). So don't attempt and be ready for the change.

The change does not need to be synchronized for deployment in either case. When you generalize the interface later, you can always provide the more specific interface for backward compatibility. But in practice any deployment will have so many changes that you'll synchronize it anyway so you don't have to test the intermediate states. I wouldn't see that as argument either.

As for documentation, either solution may be easy to use and obvious. But it stands as important argument. Implement the interface so that it will be easy to use in your actual cases. Sometimes specific may be better ans sometimes generic may be.

1

I would favour the abstract interface approach. To put a query to those kinds of (search) service is a common problem and will likely occur again. Furthermore you propably will find more service candidates which are suitable to reuse a more general interface. To be able to provide a coherent common interface for those services I would not enumerate the currently identified query parameters in the interface definition.

As it was pointed out previously - I like the opportunity to change or extend the implementation without modifing the interface. Adding another search criteria have not to be reflected in the service definition.

Although it is no question to design well-defined, concise and expresse interfaces you will always have to provide some documentation in addition. Adding the definition scope for valid search criteria is not such a burden.

1

The best summary that I've ever seen is Rusty's scale, now called Rusty's API Design manifesto. I can only strongly recomment that one. For the sake of completeness, I cite the summary of the scale from the first link (the better on top, the worser below):

Good APIs

  • It's impossible to get wrong.
  • The compiler/linker won't let you get it wrong.
  • The compiler will warn if you get it wrong.
  • The obvious use is (probably) the correct one.
  • The name tells you how to use it.
  • Do it right or it will always break at runtime.
  • Follow common convention and you'll get it right.
  • Read the documentation and you'll get it right.
  • Read the implementation and you'll get it right.
  • Read the correct mailing list thread and you'll get it right.

Bad APIs

  • Read the mailing list thread and you'll get it wrong.
  • Read the implementation and you'll get it wrong.
  • Read the documentation and you'll get it wrong.
  • Follow common convention and you'll get it wrong.
  • Do it right and it will sometimes break at runtime.
  • The name tells you how not to use it.
  • The obvious use is wrong.
  • The compiler will warn if you get it right.
  • The compiler/linker won't let you get it right.
  • It's impossible to get right.

Both details pages here and here come with an in-depth discussion of each point. It is really a must-read for API designers. Thanks Rusty, if you ever read this.

0

In layman's words:

  • The abstract approach has the advantage of allowing to built concrete methods around it.
  • The other way around is not true
2
  • UDP has the advantage of allowing you to build your own reliable streams. So why is almost everyone using TCP?
    – svick
    Commented Sep 12, 2013 at 19:03
  • There is also consideration of majority of use cases. Some cases may be needed so frequently, that it is feasible to make those cases special.
    – Roman Susi
    Commented Oct 11, 2013 at 12:53
0

If you extend the SearchCriteria idea a little bit, it can give you flexibility such as creating AND, OR etc. criteria. If you need such functionality, this would be the better approach.

Otherwise, design it for usability. Make the API easy for the people which use it. If you have some basic functions which are needed often (like search for a person by it's name), provide them directly. If advanced users need advanced searches, they can still use the SearchCriteria.

0

What is the code behind the API doing? If it is something flexible then a flexible API is good. If the code behind the API is very specific then putting a flexible face on it just means users of the API are going to be frustrated and annoyed at all the stuff the API pretends is possible but can't actually be accomplished.

For your person search example are all three fields required? If so then the criteria list is bad because it allows for a multitude of uses that just plain do not work. If not then requiring the user to specify non required inputs is bad. How likely is search by address going to be added in V2? The flexible interface makes that easier to add than the inflexible one.

Not every system needs to be an ultra flexible, trying to make everything so is Architecture Astronauting. A flexible bow shoots arrows. A flexible sword is as useful as a rubber chicken.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.