Chapter 13. Building a Robust Service

A robust service is one that feels secure and reliable to its users. Something that behaves unpredictably, sometimes gives incorrect results, and occasionally doesn’t respond at all, is not what a consumer wants to integrate into her own applications. This chapter will look at what makes a robust service, and some techniques for making services as reliable and useful as they can be, both when things are going well and when they are not.

The best services exhibit consistent, predictable behaviors. This approach of having as much “sameness” as possible works well for consumers, who start to feel at home. As they use the service, they become familiar with how it will work, and will be able to find their way around and deal with any errors they encounter more easily. Most importantly, those consumers will be able to achieve their goals, which should give both consumer and provider a warm, fuzzy feeling.

Consistency Is Key

As PHP developers, we know only too well how difficult it is to use an interface that is inconsistent. The number of manual entries that use the words “needle” and “haystack” with very little correlation between which one should come first in any given situation (and one function where they can be passed in either order!) is our reminder of how painful this can be!

In our own applications, we can do better, but it is important to pay attention to the bigger picture and the existing elements of an API while working on building more features. In particular, consideration should be given to how things are named, how the parameters are passed in and returned, and what the expected behavior should be when something unexpected happens.

Consistent and Meaningful Naming

I recently worked with a system that had a function in it called isSiteAdmin(). Guess what it returned? Wrong! It actually returned the username of the current user, or false. There are plenty of examples of badly named functions in the world, but please protect us from having any more to add to the list. Function names should be meaningful, and they should also be alike. So if there is something called getCategories() available, try to avoid adding a function called fetchPosts() or getAllTags() unless there’s a good reason for the differences. Instead, fit in with the existing convention and call the functions getPosts() and getTags().

The same applies to RESTful services, as well as those that contain function names, although it is slightly less of an issue when the clients are following hypermedia links. Look out for consistency in whether collection names are plural or not, for example.

Tip

Case-sensitive or not, make sure your service is absolutely case-consistent throughout.

The naming of parameters is also an area full of traps that are all too easy to fall into—and will annoy your users forever (or at least until you figure out how to release the next version without breaking their existing applications). The way that you name your parameters can give users a clue as to what they should be passing in. For example, a parameter called user is rather ambiguous but either user_id or username would help the user to send more accurate data through to your API.

Naming your parameters with “Hungarian notation” is probably a step too far, but aiming more at the verbose than the terse is probably in everyone’s interests. If there’s a field called desc then people will probably guess the correct meaning of the abbreviation from the context, but it is clearer to call the parameter description or descending or whatever it really means.

Common Validation Rules

The benefits of consistency were discussed already, but it is very easy to end up with slightly different validation rules for similar parameters in different settings (for example, whether extra address lines are optional or required between shipping and billing addresses). Also, try to avoid the irritatingly common situation of allowing a particular format of date/time information or telephone number in one place in your API, but not in another.

A good way to ensure that validation is always identical is to always use common functionality, including the built-in features of your framework (this may be documented as form validation but works perfectly well for API parameters too), or the fabulous Filter extension in PHP like the example in Example 13-1. The example shows a simple incoming JSON request being decoded and then filtered; if the data is not a valid email address, the value will be false.

Example 13-1. The Filter extension in PHP is a great way to validate incoming data
<?php

$clean_data = [];
$incoming = json_decode(
    file_get_contents("php://input"), true
);

$clean_data['email'] = filter_var(
    $incoming['email'],
    FILTER_VALIDATE_EMAIL
);

For data types that are specific to your application, you can create a utility class that holds all the validations. In this way, you can add functions that check for particular kinds of data, and then reuse them across your application to ensure consistency.

Predictable Structures

Structure of data is a key characteristic of a service, and a good API design will have it in mind when accepting requests, building responses, and also in the event of any error. APIs that return an array of results should always return an array of results. If there’s one result, it still needs to be in an array. If there are no results, an empty array should convey this information. Suddenly returning false, or showing an item one level up from where it would be in an equivalent endpoint, is confusing, so take care to avoid it.

In most situations, the order in which parameters are provided, either as URL parameters or as part of body content, should not matter. Whether the parameter names or their values are case-sensitive can be made clear in the documentation; it is a challenge to keep these small details correct, particularly across a large API, but it is key and does greatly improve your system.

If an error should occur, it may well be the fault of the user. That said, the API ideally should help the user understand what went wrong and how the user can be better in their use of the API (because otherwise they will log a support ticket that you will have to fix). Error responses should be in a consistent format throughout the API. If a user sees not-success in the status code that is returned with his response, he should immediately know how to get the information he needs about what went wrong, in a predictable format.

Predictability is about more than the data formats we saw in Chapter 12, although those are important too. Take care to follow patterns throughout an API regarding what happens when something is created, deleted, or not found so that the user can more quickly align themselves with your ideas and make effective use of your service.

Error Handling in APIs

Errors are a fact of life. Users will enter nonsense into your system, not because they are simpletons (although it does often look that way), but because their expectations and understanding are different from yours. The Internet is a very loosely coupled affair and all kinds of things can and will go wrong at a technical level, once in a while. How your API handles these inevitable situations is a measure of the quality and design of your API, so this section gives some pointers on what to look out for and how to do it well.

Meaningful Error Messages

We all know how frustrating it is to get error messages from systems that say something like “an unknown error has occurred.” This gives us absolutely no information at all on how we can coax the application to behave better. Even worse is an application I work with regularly, which will return the error message “Invalid permissions!” in the event that anything at all goes wrong, regardless of whether or not there is a problem with permissions. This leads to people looking in completely the wrong places for solutions and eventually filing very frustrated support tickets.

Error messages should be more than a tidy placeholder that the developer can use to find where in the code she should look when a bug is reported (there is also something to be said in favor of avoiding any copying and pasting of error messages for this reason). The information that an application returns in the event of an error is what lies between the application, the user, and the bug-reporting software. Anyone trying to use an application will have something he is trying to achieve and will be motivated to achieve that goal. If the application can return information about what exactly went wrong, then the user will adjust his attempts and try again, without bothering you. Users tend not to read documentation (developers in particular will usually only read instructions once something isn’t working—all engineers do this), so the error information is what forms their experience of the system.

When something goes wrong, answer your user’s questions:

  • Was a parameter missing or invalid? Was there an unexpected parameter? (A typo can make these two questions arise together very regularly.)

  • Was the incoming format invalid? Was it malformed or is it in a format the server does not accept?

  • Was a function called that does not exist? (For common mistakes, you might even suggest what the user may have meant.)

  • Does the system need to know who the user is before granting access? Or is this user authenticated but with insufficient privileges?

When it exists, give information about which fields are the problem, what is wrong with them, or if something is missing. It is also very helpful to users if you can collate the errors as much as possible. Sometimes, errors prevent us from proceeding any further with a request, but if, for example, one of the data fields isn’t valid, we could check all the other data fields and return that information all at once. This saves the user from untangling one mistake only to trip straight over the next one, and also shows if the errors are related and could all be fixed in one go.

What to Do When You See Errors

Let us consider our other role in that relationship: that of the consumer of a service. Many of the APIs we work with are not ones we made ourselves, so inevitably we will be encountering some of the behaviors this chapter preaches against. What can we do when this happens? The best approach is to take baby steps.

First, go back to the last known good API call. At the very early stages of working with an API, that means reading the documentation or finding a tutorial to follow, and seeing if you can make any calls at all against this system. Some APIs offer what I call a “heartbeat” method and some offer a status page. Look for something that doesn’t need authentication or any complicated parameters to call, and which will let you know that the API is actually working and the problem is at your end. Flickr has a particularly good example of this with their flickr.test.echo method.

Once it has been established that the target API is working, take a look at the call that was being attempted. Does it have any required parameters? Can the call be made in its simplest possible form, passing the smallest possible amount of data with the call? Even once things seem to be improving, it is advisable to approach changes to the API call in small steps, changing data format or adding a parameter, then checking that the response comes back as expected. Just like any kind of debugging, this iterative approach will help to pinpoint which change caused an error to occur.

While these test requests are being made, regardless of which tool is being used, take care to check the headers of the response as well as the body. Status codes, Content-Type headers, cache information, and all kinds of other snippets can be visible in the header and give clues about what is happening.

Making Design Decisions for Robustness

Robustness is basically a measure of reassurance; how does the API behave both in good and bad situations? It can be tricky to know which design patterns are the best ones to follow, especially if you are new to APIs. In that situation, good advice would be to stick to the existing standards. These are well-known and understood, and will make it easier for people to integrate with your API or web service. Writing great documentation (see Chapter 14) is key to creating a great API; in general, anything without documentation will not be a good experience for anyone using it.

Finally, always consider what should happen in the event that something goes wrong. How an API behaves in failure cases is how your users will know how robust your service is, so always aim for a consistent and predictable output, regardless of input.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset