Skip to content

Build better HTTP APIs

Sponsor: Do you build complex software systems? See how NServiceBus makes it easier to design, build, and manage software systems that use message queues to achieve loose coupling. Get started for free.

Learn more about Software Architecture & Design.
Join thousands of developers getting weekly updates to increase your understanding of software architecture and design concepts.


Many of the business systems we build are for managing life cycles and workflows. It provides ways to make the state transition from beginning to end. One of the challenges when building systems is that they evolve, and you can start leaking business logic to the frontend or clients where you end up with duplicate logic littered all over the place. Here’s a way to solve that issue so you can build better HTTP APIs.

YouTube

Check out my YouTube channel, where I post all kinds of content accompanying my posts, including this video showing everything in this post.

State Machine

For this blog/video, I’m going to use a toaster to represent a state machine. Many of the systems we’re building involve state transitions and workflows.

Here are the OpenAPI operations that are available.

You can get the toaster’s state, turn it on or off, and set the strength. The only meaningful state transitions are from on to off and vice versa.

Now, you’ve probably integrated with a 3rd party that possibly used OpenAPI or just had general documentation about their API and all the available operations and payloads.

There also might have been some documentation about what operations were permitted based on the system’s state. For example, with my toaster, you cannot call to turn on the toaster if it’s already on.

Implied at Design Time

As you build out the client interacting with that API, you’re using the knowledge from the documentation or when developing (aka design-time) to determine how your client will use the API.

With my toaster as the example, you might build out the UI (client) so that you can only turn on the toaster when it’s in an “off” state.

This means you’re baking logic based on the state that’s returned from the API into your client with the knowledge you have at design time.

But this means if the API evolves and changes its rules, your client is not in sync with those rules. The best-case scenario is that your client doesn’t break. Or that the UI throws errors when specific actions are performed, which shouldn’t be because rules have changed in the API. To deal with this at the API level, you might be tempted to version.

With my toaster, the only rule is that you can’t turn it on if it’s already on. It allows you to change the strength at any given time. If that rule changes, where we only let you change the strength if the power is off, our client will probably throw an error if it tries to change the strength when it’s on. Which previously would have worked fine. Rather, we’d probably have our client not even let the user perform the action.

But because we’ve baked all this logic in at design time, which looks at the state of the system to determine what he client can/can’t do, any changes in rules in our API won’t be reflected in the UI.

Explicit at Runtime

A solution to this is to be explicit to your clients about the operations they can perform at runtime. Instead of the client looking at the state of the system, and having that baked-in logic and implying what can be done.

Be explicit about the operations the client can perform at runtime.

When we call the GET /toaster, we receive in the response a list (array) of applicable operations given the state of the toaster. Here’s the example response.

The list of operations tells us at runtime from the response that we can call PowerOn and Strength. Those operationIDs in the response are matched to the operationID in our OpenAPI.

At design time, when we’re building our client, we can use this information so that it behaves dynamically based on the responses.

This will allow our API to evolve without having to change our Client. For example, our client could then look at the operations and determine which UI elements to show based on the available operations.

This means that if we change the rules that only allow changing the strength when the toaster is off, our client won’t have anything to change, and it will only show the UI element to change the strength when that operation is available.

Another side effect is that since we’re running the URI alongside the operation, we don’t have to construct URIs for our client since our response contains them. This also allows for more evolvability on our API side.

We can use those URIs provided directly as the call to the API from our client. You can take this further and also provide the HTTP Method.

At design time (coding), you/the developer would implement all the operations based on the documentation or OpenAPI and let the response tell you which operations you can perform at runtime.

You’re mixing design time and runtime for evolvability to build better HTTP APIs.

Join CodeOpinon!
Developer-level members of my Patreon or YouTube channel get access to a private Discord server to chat with other developers about Software Architecture and Design and access to source code for any working demo application I post on my blog or YouTube. Check out my Patreon or YouTube Membership for more info.

Leave a Reply

Your email address will not be published. Required fields are marked *