A Structured Approach to Designing Intent-Based APIs

Dominic C
PointsBet
Published in
7 min readFeb 4, 2022

--

By Rich Tervet on Unsplash

Earlier this month, I wrote an article explaining the concept of Intent-Based APIs, however, it was a very surface-level explanation and I feel it could have done with explaining some of the structure around the concept and the benefits gained from structuring it in such a way. Nonetheless, I believe it serves as a good primer on the concept, and within this article, I plan to explore the idea further. Let’s get into it!

Looking at Intent Resources

In most REST APIs, a resource directly maps to an object, and as such all a resource represents is the state of that object. It’s in the name, “Representational State Transfer”, the request is built to represent the new state of the resource. The most natural way to do this is via CRUD, which coincides neatly with the most commonly used HTTP methods. This is inherently a limiting factor, all a user can do is manipulate the properties of these resources in an attempt to mimic business operations. And for most APIs, that’s fine. If a user needs to update the name of a recipe, then they can quite easily. But as I mentioned in my previous article, once the domain grows in complexity, it becomes a different story.

If we abstract the concept of a resource away from an object's state, we can look at the concept of an Intent Resource, or a resource that maps to a user intent or action. Conceptually, I believe this is the synthesis between RPC and REST that Herman Peerman was alluding to when he wrote about his short blog post, Beyond Rest and RPC. Keeping (mostly) in-line with the rules of REST, the request is representative of the user's intent, which directly maps to the new state behind the scenes. Instead of dealing with minor state changes, consumers are now utilizing the ubiquitous language of the domain to perform business actions. They are no longer modifying state with the intent to perform some function of the business, but instead, they are enacting the intent of performing a function of the business.

Modeling Intent Resources

When modeling regular resources within a REST API, it makes sense to use nouns for naming, however, when dealing with intent resources it’s more fitting to model resources with verbs. This forces the API to be designed at a higher level, closer to core business flows rather than the objects that those flows manipulate. Although an example was touched on in my previous article, another popular example when discussing intent APIs is the concept of a transfers resource.

In a CRUD API, a transfer between accounts may look like so:

PUT /User/{UserId}
{
"Balance": 20 // Down from 40
... //other user data removed for brevity
}
PUT /User/{UserId}
{
"Balance": 100 // Up from 80
... //other user data removed for brevity
}

In an intent API, a transfer between accounts may look like so:

POST /Transfers
{
"FromUserId": 1,
"ToUserId": 2,
"Amount": 20
}

Here, we model the verb representing the resource to the business action it represents, and we model the payload it receives to the bare minimum required for that functionality. This allows us to shift the responsibility of both the resource and the payload closer to the intent of the user, and further from the abstract concept of purely editing a singular object's state.

Truthfully, there’s not a lot to modeling an intent-based resource other than knowing when you should consider doing so: A lot of the other semantics and implementation details you often worry about in an API are still bound by the constraints of REST. Although it requires developers to shift their perspective to a different style of resource, apart from that the rest of the design is quite standard. To get a better understanding of when to use an intent-based API, it’s handy to understand some of the benefits that come with it. So, let’s explore why reasoning about an API with resources modeled like the later example may be beneficial for both developers and organizations.

The Benefits of Exposing Intent Resources

Intent resources aren’t supposed to be some kind of new standard to creating REST APIs, but instead, they’re a pattern for abstracting complex domain concepts away from the API and the consumers. An API can be viewed as a platform that connects data producers and data consumers, and as such, this connection should feel natural, easy. There’s no perfect method to structuring an intent-based API, unfortunately, no one’s written a dissertation on the topic or any fancy white paper about the theory of intents. That being said, if we can enumerate some handy benefits that come with using an intent-based API, then we can pick the right time to use them. So, let’s do that, shall we?

Pulling the Business Logic Away from the Consumer

Looking back at the transfer example, it is clearly evident that the CRUD style approach requires some form of knowledge on how business events are structured and the internal implementation of the API works. Or, it would be clearly evident if this wasn’t an incredibly simplistic example in an article, I promise. Scaling this problem up to more complex APIs, the burden on the consumer to understand the intricacies of the domain and implementation grows, as CRUD requests must be orchestrated just so as to mimic the actual intent of the user. Furthermore, the CRUD approach required sequential calls, with the sequence bearing importance. On top of this, there would need to be more checks in place to ensure the first call didn’t fail before the second call was made, and so forth. This burden of business logic and added transactional overhead is abstracted away behind the intent resource for transfer, things like sequential calls and extra checks can now be taken care of by the system it is directly pertinent to, instead of the consumer.

Fine-Grained Actions vs Coarse-Grained Objects, or “Fewer Reasons to Change, Fewer Chances for Things to Break”

If we look back to the examples above, the data required to establish a transfer between accounts can be minimized heavily by taking the intent-based route. In a large business, the User domain object may contain many fields, with each field possibly holding importance in terms of business logic and how the object is structured. By requiring the entire payload in a CRUD request such as PUT, the contracts that both consumers and producers utilize become fragile: Both sides are dependent on every field regardless of if they use them. If the producer happens to change a field, the consumer may start receiving bad requests from the producer, even if they don’t use that field.

Intent APIs alleviate this by minimizing the reasons to change: The number of fields within the transfer object is minuscule when compared to the potential of a user object, and furthermore, if the payload needs to change, then it is inherently fitting and a change that genuinely should be coordinated with consumers. This is a two-pronged point, as not only does this generate resilient and future-proof contracts, but it also creates manageable and targeted payloads and responses, which helps the discoverability of the functionality of a platform and incorporating it into the consumers' service.

Examples in Production

Although it’s well and good to see the benefits on paper, it’s nice to see the proof of its worth in a production environment. As mentioned earlier, GitHub’s API has several examples of brilliant in-production intent-based APIs. Just look at the contract for their merges, which return back a commit resource:

POST /repos/{owner}/{repo}/merges
{
owner: 'octocat',
repo: 'hello-world',
base: 'base',
head: 'head'
}

But just because one company doing it, doesn’t mean it’s feasible, right? Well, that’s debatable, but we can explore a few more productionised intent APIs quite easily.

Stripe, for example, works with an aptly named style of resource they deem as intents. These intents can be seen as a multi-step process of sorts, with users publishing or POSTing their intent to make a payment, and confirming it down the track by attaching their payment method in a subsequent call.

Although both approaches showcase two ways of utilizing intent-based APIs, they prove that they can work well in a production environment at scale, with GitHub’s API, in particular, showcasing many different examples of reifying user intent into actionable resources.

Conclusion

REST exists as a set of architectural constraints used to guide the design of an API, however often so it is conflated with the concept of CRUD-like interfaces, which I personally believe limits users in designing an API that correctly solves the business problem at hand. This issue is only exacerbated by the trend of REST purism that has become more prevalent over the years. Modeling an endpoint around an intent resource is a tried and true way method of designing APIs that holds many benefits that may not be obtained through a traditional CRUD API, and exists as yet another tool for developers to have within their handy-dandy toolbox for the next time they need to solve a problem with an API.

Resources

--

--