back to index

Idempotence and Side Effects

What happens when a user pounds the submit button — designing for idempotent APIs.

published Mar 22, 2019 tags #api-design #http #rest

~/posts/idempotence-and-side-effects $ cat post.md

/ LANG EN / 中文
/ THEME / /

Users like to click things repeatedly

User behavior is not predictable. The same action being triggered dozens of times in a row is normal.

Consider: a user POSTs to create an article. The API is probably something like POST https://domain.com/article/create, or the cleaner-REST POST https://domain.com/article.

A user can pound the submit button 100 times if they want.

Side-effect-free functions

Part of the appeal of functional programming is the absence of side effects: same input, same output, calling once or a hundred times yields the same result, and you can discard everything but the last call’s output.

Try to apply that to the POST: “when some inputs match, return the same result with no side effects.” That doesn’t work — creating an article is the side effect; the API exists precisely to perform it.

Middleware

There are always options. One is middleware: route every user request through a middleware layer that handles the actual write via a distributed transaction. When the middleware sees the user submitting “the same article,” it intercepts. No matter how many times the user clicks, only one article gets created.

The downsides:

  • “Same article” is fuzzy. Identical text is obvious. What if the user changes a character? A few characters? Nobody has a clean answer.
  • Not every service warrants this much architecture. Distributed transactions + a middleware tier is a lot of machinery.

Back up — the real issue is the semantics of the request. The POST as written is too vague; “what counts as a duplicate?” can’t be answered from the inputs alone.

Idempotence

The definition: N applications produce the same result as a single application. A request, executed once or many times, leaves the same side effect behind.

When designing user-facing systems, idempotence is a core concept, and HTTP is the natural stage for it.

HTTP method semantics

Classic interview question: how do GET and POST differ? One answer: GET should be side-effect-free — the same GET, against an unchanged resource, always returns the same result.

The same idea extends to other methods. POST vs PUT is the one people mix up. “PUT updates, POST creates” misses the point — POST is a general-purpose verb. Semantically, PUT should target a specific resource: given a URI, PUT replaces/updates the state of the resource to match the request body. The same PUT with the same body should always yield the same result — i.e. idempotent.

A solution

Back to the example. Without standing up middleware, split “create article” into two steps:

  1. POST https://domain.com/article/create — returns 201 with a new article ID. At this point the article is empty: it has no content, doesn’t show up in the user’s listing, and self-destructs after a timeout if nothing is filled in.
  2. PUT https://domain.com/article/{id} — write the content into that ID.

Now the user can pound the button — each click gets a fresh ID, but only the article that actually receives a PUT survives. Push it further and make the PUT idempotent too: the same content PUT’d multiple times yields the same article.

back to index