Aim to change your API as frequently as you change your business model.

There’s an old saying in Tennessee. I know it’s in Texas — probably in Tennessee — that says “Move fast and break shit once, product gets released. Move fast and break shit… you don’t move fast again.”

Points of integration have mass.

The more inter-dependent products & APIs you have, the harder it is to make big changes to them. Changing an API that’s in use always runs the risk of breaking older product versions. Adding new features to your product sometimes requires API changes.

Because of this relationship, it’s easy to crank out a few product versions smoothly, only to find that the next version will require a significant coordination effort to enable the features you want without breaking something your existing customers are paying you for.

Repeat this cycle enough times, and you may find that it now takes 3 months to release what would have previously only taken 3 weeks.

Your API is the interface to your company’s state.

Whether your API is comprised of many independent (micro?)services or is implemented as a monolith doesn’t matter here. Either way, it provides an interface for accessing and interacting with the resources and domains of your business.

This way of thinking about an API is intentionally abstract. You should aim to model your API such that it closely resembles your business model. If your business model involves selling tickets for events to customers, then your API might expose the resource types `Customer`, `Event`, `Purchase` and `Ticket`.

If you model each resource type such that all of its details that are relevant for your business are represented, then you won’t need to change them until your business model changes.

Your product should never consume RPC endpoints.

While the resource types that make up your business model change infrequently, the way your product represents itself to your users probably evolves much faster. As a result, the nature of procedures is something you will likely want to change at some point. Don’t add artifacts of procedures to the interface to your business .

What about `POST /signIn`? What about `POST /sendPushNotification`? There’s always a better way.

What is the significance of these procedures relative to the lifecycle of the consumer’s interaction with your API? If the answer is ‘none’, then you’re better off implementing it as a side-effect of another transaction. Otherwise, you should find a way to represent that significance as a resource. Let’s look at an example.

For `POST /sendPushNotification`, unless your core business model involves sending push notifications (hint: even if it is, don’t use an RPC), you shouldn’t expose this capability directly via your API. If you want to send a push notification to a user’s friends when they buy a ticket to an event, you should model it as the implicit side effect of the purchase of a ticket (e.g., it happens after `POST /purchases`) succeeds.

For `POST /signIn`… SignIn (a verb) is not a resource type! It’s unlikely that you can assert (without appearing English-illiterate) that the interface to your business involves customers having many “SignIns”. Instead, think of a way to convert the interface-related significance of that procedure into a resource. A better approach would be to have a Session resource, which consumers can create by sending a fragment of a User (e.g., containing `username` and `password` fields) to `POST /sessions`. Upon success, the API would respond with a new Session object (HTTP 201) and a HTTP session that corresponds to the new Session object. All subsequent requests made with that HTTP session would authenticate as the User whose fragment was used to create the Session. Notice that the resourceful alternative to the RPC call represents sound logic (beyond “do the thing called ‘signIn’”).

If the side effect of any transaction involves some RPC call, that should be an implementation detail, not part of your interface. Enforcing the discipline to keep RPC artifacts out of your interface will significantly reduce the amount of changes you’ll need to make to it over time.

CRUD + Policies & Side Effects.

Why bother having transaction types beyond CRUD? Your API is complete when it makes reachable all valid states of your business. If you implement a resource-oriented interface, all states are reachable by CRUDing your API’s resource types.

It’s likely you’ll want to impose business and security constraints to your transactions, for instance, people shouldn’t freely access/change/delete other people’s sensitive information. You can express these constraints simply via policies.

A policy is a function that takes an actor (the user making the request), and a target resource (the resource that the request is accessing/manipulating), and determines whether the actor should be allowed to complete the transaction, or should be denied. For example, you can implement policies for checking if the actor owns the target resource, or if the actor is blocked by the owner of the target resource.

A policy should be treated as a middleware to your transaction rather than part of the implementation of your transaction. If your transaction creates a Post, then it should be implemented such that it assumes the actor is allowed to create the Post. If an actor tries to create a Post under conditions that the transaction should not be allowed, the actor will be denied via the policy before the transaction function is invoked.

Everything else can be modeled as a side effect. Push notifications, transactional emails, etc. Keep them out of your transaction implementation. Instead, implement them as functions that are invoked after your transaction succeeds (or in some cases, after it fails… e.g., “Someone was challenged when trying to sign into your account”).

Lastly, if your API is based on HTTP (as opposed to GraphQL [protocol agnostic] or some other interface), forget about whoever said “POST is used for non-idempotent resource creation and anything that isn’t covered by the other HTTP methods”. The only thing that exception excuses is RPC-style methods. Don’t do it.