Explanation: Why Idempotency Matters
In distributed systems, networks are inherently unreliable. A client might send a request to a server, the server might process it successfully, but the connection drops before the server can send the HTTP 200 OK response back to the client.
From the client's perspective, the request timed out or failed. What happens next is critical to system integrity.
The Double-Charge Problem
Imagine a user clicks "Pay $100" on an invoice.
- The browser sends
POST /api/v1/rpc/Invoice/pay. - The server charges the user's credit card and marks the invoice as paid.
- The server attempts to respond, but the user's WiFi drops for a split second.
- The browser throws a
NetworkError. - The user, seeing an error, clicks "Pay $100" again.
Without idempotency, the server receives the second request, blindly executes the logic again, and the user's credit card is charged $200.
The Idempotency Contract
An idempotent operation is one that produces the same result whether it is executed once or multiple times.
While GET, PUT, and DELETE requests are generally expected to be idempotent by HTTP design, POST requests (which create new resources or trigger actions) are explicitly not idempotent.
Framework M solves this by establishing a strict contract via the Idempotency-Key header:
- Client Responsibility: The client generates a unique UUIDv4 for a specific user action (e.g., clicking a specific "Pay" button) and sends it in the
Idempotency-Keyheader. If the client retries the exact same action, it must send the exact same UUIDv4. - Server Responsibility: The server intercepts this key before hitting the business logic.
- If it's a new key, the server processes the request and caches the final response (Status 200, Body, Headers) tied to that key.
- If the key has been seen before, the server skips the business logic entirely and immediately returns the cached response.
Why Built-in to Framework M?
Historically in Frappe, developers had to manually write logic to check if a document was already submitted or a payment already recorded. This led to subtle race conditions and required boilerplate in every controller.
In Framework M, idempotency is pushed out of the business logic and into the infrastructure layer (the IdempotencyMiddleware and the UI useCall hook).
As an application developer, you can write your business logic assuming it will only run exactly once per user intent, massively simplifying your code and reducing catastrophic edge cases.