Avoiding Sh*tty Interfaces And Building A Better Taco Service

Seen Better Days
Sh*tty Interface 

Software evolves organically, regardless of how much so-called up-front design you put into it. This is especially true with so-called Agile development where you take requirements one step at a time. Who hasn’t seen this happen while iterating on an interface:

Day 1: TacoService initial design

/**
* Returns a Taco based on the type of meat passed in.
**/
Taco getTaco(String meat);

Day 2: TacoService refactored to support vegetarian options

/**
* Returns a Taco based on the type of meat passed in.
* If vegetarian flag is true, meat may be null.
**/
Taco getTaco(String meat, boolean isVegetarian)

Day 3: TacoService gets a vegan intervention

/**
* Returns a Taco based on the type of meat passed in.
* If vegetarian flag is true, meat may be null.
* If vegan flag is true it supersedes vegetarian and all other options.
**/
Taco getTaco(String meat, boolean isVegetarian, boolean isVegan)

Day 4: It’s not a party until the Gluten-free needs are introduced

/**
* Returns a Taco based on the type of meat passed in.
* If vegetarian flag is true, meat may be null.
* If vegan flag is true it supersedes vegetarian and all other options.
* If glutenfree flag is true, meat is allowed but vegan
* and vegetarian flags are honored.
**/
Taco getTaco(String meat, boolean isVegetarian, boolean isVegan, boolean isGlutenFree)

Day 5: QA brings up the scenario of the picky eater.

/**
* Returns a Taco based on the type of meat passed in.
* If vegetarian flag is true, meat may be null.
* If vegan flag is true it supersedes vegetarian and all other options.
* If glutenfree flag is true, meat is allowed but vegan
* and vegetarian flags are honored. A list of acceptable toppings can
* be optionally provided, otherwise all toppings are included.
**/
Taco getTaco(String meat, boolean isVegetarian, boolean isVegan, boolean isGlutenFree, List<Topping> toppings)

What we have here is called “Parameter Creep”. Rest assured, it will never end, some other dietary fad or innovation in Mexican cuisine will provide more reasons to add even more options. Let’s recap why this interface is getting shitty:

  1. it’s always changing as requirements change (breaking the interface)
  2. the method signature is going off the page (party foul)
  3. it is probable most arguments are not even needed by the majority of callers (solving for the 20%)
  4. you might as well make the taco yourself with the effort it takes to make the call

There are a few ways we can mitigate some of this. Obviously we could keep each and every one of these signatures and have a TacoService that looks like this:

Taco getTaco(String meat)
Taco getTaco(String meat, boolean isVegetarian)
Taco getTaco(String meat, boolean isVegetarian, boolean isVegan)
Taco getTaco(String meat, boolean isVegetarian, boolean isVegan, boolean isGlutenFree)
Taco getTaco(String meat, boolean isVegetarian, boolean isVegan, boolean isGlutenFree, List<Topping> toppings)

You probably see this a lot, especially in framework code that is trying to solve all things for all scenarios without breaking existing contracts (i.e., the method overloads keep expanding or changing the arguments to provide new functionality without breaking existing code). The issue with this is not knowing which API to call. If I want a vegetarian taco what do I provide for meat parameter, and what if I want toppings but don’t care about the intermediate options? This is a shitty interface.

Speaking of contracts, the point of an interface is to provide a contract to calling code. The contract should be stable and not break existing clients if possible, yet it should be flexible enough to adapt to change.

So on to the first way to improve this progressively shitty interface: The Messenger Object, or Data Transfer Object (DTO). Simply put, the Messenger or DTO encapsulates the required parameters for the method. For example in the example above we could replace all method signatures with the following:

Taco getTaco(TacoRequest request);

You can of course imagine that a TacoRequest has properties of:

String meat;
boolean isVegetarian;
boolean isVegan;
boolean isGlutenFree;
List<Topping> toppings;

The astute will begin the counter-argument that while you may have just cleaned up the TacoService (great), you moved the problem to the DTO. How? Well also imagine all the constructors you could construct to make DTO construction more “convenient”. They are essentially the same as method parameters we tried to simplify. The astute would be absolutely correct. We did move the problem, but is it a problem? Let’s look at the context of the code that would call this interface: a client request from some kind of user interface no doubt. The UI collects the user’s order and maps that to a DTO, using either convenience constructors or simply setting the properties that are relevant. This part is non-negotiable, has to be done regardless. Now, is there any doubt what method to call on the TacoService? Is there any doubt about the contract the service provides? Can we add additional properties to the request without breaking the interface? Like it or not we are in a better place, but it could be better.

There are various ways to return results for method calls – from error codes, to objects, to callbacks, to nothing at all in a publish and subscribe scenario. Each and every context merits considering which pattern makes the most sense, however I have found that in general, a Service should be obligated to indicate the status of the request it processed on behalf of the caller, as well as any data it was to return to it as part of the result. If you don’t do this, you end up with awkward contracts like we have with our TacoService.getTaco() that returns a Taco, but what if there’s a problem (ingredients missing, chef on strike, kitchen on fire)? Do you get an empty Taco, null, an exception thrown? There’s a school of thought espousing throwing business exceptions when something bad happens, if you are of that school, you’re going to be sorely disappointed with what I am about to advocate. In any case, let’s first rule out returning null or just error codes.

Returning either the object or null works in the happy case when Tacos are readily available, but when one of many error conditions occurs all the service can do is return null. It is now up to the client to determine what that means, which of course it can’t because the other side of the line essentially hung up on them. Was it bad inputs? Was there a temporary failure or is the service down hard? Were they out of cilantro? This is still a shitty interface.

We can make this better by returning error codes. Error codes can provide a means to communicate more details about failure conditions to the client. How do you return an error code for a method that is intended to return something (a Taco) to the client? In/Out parameters? Another method to get results? It’s tempting at this point to add an errorCode property to Taco itself. But are errorCodes part of the Taco “Domain”? Nope, they aren’t, they are cross cutting, so don’t even think about it.

Let’s look at the publish and subscribe and callback scenarios – both return a response asynchronously which can have it’s advantages, however in the end we face the same problem – if it works we get a Taco, if not, we get nothing…

Step away from Object-oriented programming for a second, move up a few layers to the Web and you will find that there’s a relatively elegant solution staring right at us. The web has standardized on Http Status codes, headers, and bodies. This provides the flexibility to return just a status code, a status code with headers, or a status code, headers and a body with additional information (either a machine-readable payload or user-readable HTML).

In the Object-oriented world we can do something similar – create a Response object than can convey status code and message, along with a (optional) payload if the call was successful. For example:

public class ServiceResponse<T> {
    int statusCode = 200;
    String errorMessage = "";
    T payload;
}

This allows any service method to provide additional details as well as the desired payload. So now our interface can look like this:

ServiceResponse<Taco> getTaco(TacoRequest request);

The caller can now assume that a statusCode of 200 will yield a payload (yum) and anything else will indicate something bad happened (actually tragic) and depending on the errorCode, the client may prompt the user to try again, for example in the case where an ingredient is no longer available. If the kitchen is on fire, maybe a 500 comes back.

So now we have a request, a response, and all is well except we forgot to include any context for the call. How you get access to the calling context will vary based on your runtime and framework. One option is to include the context in the request object, or play a similar trick where you have a ServiceRequest object that takes in a request object (our taco parameters) as well as standard context info (like userId, etc).

Voilà, a beautiful interface for Tacos, a clear contract, error handling, and a re-usable pattern for other services. And Tacos.

Avoiding Sh*tty Interfaces And Building A Better Taco Service