API Design — Handling exceptions

Design for errors, report defects

There are two kinds of exceptions.

  • Errors — expected states that we can design for
    - ex: device not connected to internet
    - ex: user provided incorrect credentials
  • Defects — unexpected states that the application does not know how to handle. These are bugs that developers failed to account for. Report them or better yet, crash the application to increase the chances that this defect does not make it to production. Log statements are easily missed when your UI fails silently.
Design for errors by building them into the return values of our APIs (methods)

The Naive API

Method signature of a naive API. Notice it directly returns a `User` — the ideal case

Let’s use our NaiveApi!

Surprise! .login(credentials) threw an exception because we provided the wrong credentials. How can we improve this?

The Better API

Method signature of a better api. Notice it directly returns a `LoginResponse`

What is a LoginResponse? It represents all possible outcomes of a login request. Let’s dig deeper:

`LoginResponse` — A return type that can represent our ideal use case and possible errors.

Usage now becomes:

Developer is encouraged to handle the ideal success case and error case

Handling different types of errors

Let’s take a look at what the Error object really is.

The abstract `Error` class, with three different implementations

The application could:

  • try again in when the device regains connectivity
  • prompt the user to enter correct credentials.

Here’s usage of the BetterApi that accounts for different types of errors.

Type-safe error handling — the developer is forced to handle each of the 3 different error implementations

The developer passes the following to error.handle():

  • 3 separate functions able to handle each of the 3 error types
  • Only the function that map to the actual error type will be called. If the error is an instance of invalidCredentials, then only its function will be called (first parameter of error.handle).

The static extension method error.handle() enforces type safety as well. What does it look like?

A static extension function that takes the abstract `Error` along with functions to handle each of the 3 implementations as parameters

Additional benefit: extra resilience to adding new implementations of Error implementations.

Bonus points: Kotlin Sealed Classes

One of the Kotlin’s best features is its sealed classes, which can give us compile time type-safety.

Usage now becomes:

Sealed classes remove a lot of verbosity!

More Bonus Points: Rx

With proper dependency management and scoping, you can expose all login attempts as observables for easy consumption from your UI.

Observe different login requests from anywhere in your application.

Conclusion

Building errors into the return value of API calls makes all possibilities more obvious to the developer, allowing the application to robustly handle each scenario.

Shoutout to Paco Estevez who got me to really think about this.

Source is available on GitHub.


Catch the conversation on reddit
Part 1: Api Design — Temporal Coupling
Follow me on Twitter! @zaktaccardi