Skip to content

Archive

API Design

35 articles
Software Engineering 20 Sep 2026 8 min read

Idempotency Keys Make Retried Commands Safe

A client can lose the response to a command even when the server completed the operation. The connection may close after a payment is recorded, a job is created, or an order is accepted. From the client’s point of view, timeout does not reveal whether the command failed before execution or succeeded before the response disappeared. Retrying is necessary for availability, but repeating a state-changing command can duplicate the side effect. An idempotency key gives all attempts for one logical command the same identity. The server stores the outcome associated with that identity and reuses it when the same command arrives again.

Software Engineering 19 Sep 2026 7 min read

Idempotency Keys Bind Retries to One Logical Mutation

A client can lose an HTTP response after the server has committed the requested mutation. From the client’s perspective, the operation is unresolved: the connection failed, but that failure does not reveal whether durable state changed. Retrying the same POST can then create a second order, payment attempt, reservation, or other mutation. An idempotency key gives the retry a stable identity that is separate from any single transport attempt. The server can associate repeated requests carrying that identity with one logical operation. That mechanism narrows an ambiguity at the API boundary, but the key alone is not a guarantee. Its scope, persistence, request comparison, concurrency control, and replay policy determine what repeated delivery actually means.

Software Engineering 19 Sep 2026 6 min read

ETag Preconditions Prevent Lost Writes in HTTP Update APIs

Two clients can read the same resource, edit different fields, and send updates seconds apart. If the server accepts both writes without checking which representation each client edited, the later request can silently replace state written by the earlier one. The transport succeeded, yet the application lost a concurrent change. HTTP provides a conditional request mechanism for this boundary. A server can attach an entity tag to a representation, and a client can return that tag in If-Match when submitting a state-changing request. The update proceeds only while the selected representation still satisfies the supplied precondition.

Software Engineering 15 Sep 2026 8 min read

HTTP Preconditions Turn Resource Versions Into Write Guards

A client reads a resource at revision 41, edits one field, and sends the whole representation back. During that interval, another client commits revision 42. If the server accepts the first client’s replacement without testing its source revision, revision 42 can disappear from the visible state even though both requests completed normally. This is the lost-update shape at an HTTP boundary. The notable detail is not simultaneous execution. The requests can arrive seconds apart. The conflict exists because a later mutation was derived from an earlier representation and the server has no condition connecting those two facts.

Software Engineering 12 Sep 2026 8 min read

Conditional HTTP Writes with Entity Tags

Conditional HTTP Writes with Entity Tags A client reads a resource, edits its local copy, and sends a replacement several seconds later. During that interval another client may have committed a different replacement. A plain PUT has no statement about the representation on which the edit was based, so the server can accept a request whose starting state is already obsolete. HTTP conditional requests can carry that missing premise. A response entity tag identifies a selected representation, and If-Match makes a later request conditional on a current representation matching one of the supplied tags. For state-changing methods, that turns representation identity into an explicit concurrency boundary.

Software Engineering 11 Sep 2026 7 min read

Command-Query Separation for Predictable Methods

Command-Query Separation for Predictable Methods A method named getBalance() looks harmless. A caller expects it to report a value. If calling it also recalculates fees, updates an account, and writes an audit record, that caller has to understand much more than the name suggests. Command-query separation is a design principle for avoiding this kind of surprise. A command asks the system to change state. A query asks for information and does not change observable state. Keeping those responsibilities separate makes call sites easier to reason about and gives method contracts a clearer shape.

Software Engineering 10 Sep 2026 11 min read

Tolerant Reader Pattern for Evolving Contracts

Tolerant Reader Pattern for Evolving Contracts A producer adds an optional field to a response. No existing meaning changed, yet an older consumer starts rejecting every message because its parser expected exactly five fields. The producer made a seemingly compatible change, but the consumer had quietly coupled itself to details it never used. The Tolerant Reader pattern addresses that problem from the consumer side. A tolerant reader describes and validates the information it actually depends on while allowing unrelated parts of an incoming representation to vary. Used carefully, this makes contracts easier to evolve without turning validation into guesswork.

Software Engineering 10 Sep 2026 8 min read

Command-Query Separation: Make Side Effects Visible

Command-Query Separation: Make Side Effects Visible A method named getNextInvoiceNumber() looks like a read. If calling it also increments the stored number, logging it twice can change application behavior. A debugger expression can consume a value. A harmless-looking retry can advance state again. The problem isn’t mutation by itself. Software has to change state. The problem is making a caller guess whether asking for information also changes something. Command-query separation is a design principle that reduces this ambiguity. A query returns information without changing observable state. A command changes state and doesn’t need to return domain information about that change. This article shows how that distinction makes APIs easier to reason about, where the rule is useful, and where forcing it creates more complexity than it removes.

Software Engineering 09 Sep 2026 9 min read

Replacing Boolean Parameters with Explicit Choices

A call such as sendReport(report, true) may be perfectly valid code, yet it makes the reader stop. What does true mean? Send immediately? Include attachments? Compress the report? The answer exists somewhere in the called function’s contract, but it is not visible at the call site. Boolean parameters become a design problem when they represent an important choice between behaviors. They compress that choice into true or false, so callers must remember what each value means and the implementation often grows branches around the flag.

Software Engineering 09 Sep 2026 8 min read

Reducing Positional Coupling in Function Calls

A function can be perfectly implemented and still be easy to call incorrectly. One common cause is a parameter list in which several values have the same representation and their meaning depends mainly on position. Consider a reporting function that accepts three dates. At the call site, the values may all look valid even when two of them are accidentally reversed. A compiler or type checker often cannot help because each argument still has the expected type.

Software Engineering 09 Sep 2026 11 min read

Propagating Deadlines Through Call Chains

A request can have a timeout at every network call and still take far longer than the caller intended. The problem appears when each layer starts a fresh timeout. A frontend gives service A 800 milliseconds. Service A spends 300 milliseconds doing local work, then gives service B another 800 milliseconds. Service B spends 250 milliseconds and gives service C yet another 800 milliseconds. Every individual timeout looks reasonable, but the chain no longer has an 800-millisecond limit.

Software Engineering 07 Sep 2026 10 min read

Reducing Temporal Coupling by Making Order Explicit

Some APIs look simple because each method is simple. The difficulty appears only when you try to use them correctly: one method must run before another, a third method is legal only after some state change, and cleanup must happen at the end. The code is then coupled not only to what operations exist, but also to when they happen. This is temporal coupling: correctness depends on operations occurring in a particular order. Some ordering is unavoidable. You cannot read a file before opening it, and a transaction cannot commit before it begins. The design problem is hidden or unnecessarily fragile ordering, where callers must remember rules that the API does not make clear.

Software Engineering 07 Sep 2026 9 min read

Designing Strict Input Contracts

Being tolerant of imperfect input can look helpful. A parser silently fixes an invalid value, an API treats an unknown option as a default, or a service accepts several spellings for the same field. The immediate caller succeeds instead of receiving an error. The cost often appears later. Once clients discover that invalid input is accepted, they may depend on that behavior. Tightening validation then becomes a compatibility change, and different implementations may interpret the same malformed input differently.

Software Engineering 07 Sep 2026 10 min read

Designing APIs for Observable Behavior

An API can keep every documented promise and still break its users. Suppose a function returns search results with no documented ordering guarantee. Its current implementation happens to return items alphabetically. A client notices that behavior and removes its own sorting step. Months later, the implementation changes and returns the same items in a different order. The API still satisfies its written contract, but the client breaks. This is the practical problem behind Hyrum’s Law: when an API has enough consumers, some consumers are likely to depend on almost any observable behavior, whether or not that behavior was intended as part of the contract.

Software Engineering 06 Sep 2026 8 min read

Reducing Temporal Coupling in APIs

Some APIs look simple because each method is simple. The difficulty appears only when you try to use them: one method must run before another, initialization must happen at exactly the right time, and an innocent-looking call fails because an earlier step was missed. This kind of order dependency is called temporal coupling. Two operations are temporally coupled when their correctness depends on when or in what order they happen. Some ordering is inherent to the problem, but hidden ordering makes code harder to understand, test, and change.

Software Engineering 06 Sep 2026 8 min read

Grouping Related Parameters into a Parameter Object

A function can have individually reasonable parameters and still be difficult to use correctly. The problem often appears when several values travel together through many calls and only make sense as a group. Consider code that repeatedly passes startTime, endTime, and timezone. Each value has a clear type, but callers must remember their relationship: the end must not precede the start, and both timestamps are interpreted using the same timezone rule.

Software Engineering 06 Sep 2026 9 min read

Designing Tolerant Readers for Evolving Contracts

A service reads a response from another system. It needs two fields, but its deserializer models twenty. A harmless producer change adds a field, changes an unused field, or expands an enum that the consumer never acts on. The consumer still breaks because it accidentally depended on more of the contract than its job required. A tolerant reader avoids that unnecessary coupling. It reads the smallest part of an external representation that the consumer needs and rejects changes only when they threaten assumptions the consumer actually relies on.

Software Engineering 06 Sep 2026 9 min read

Designing APIs with Preconditions and Postconditions

An API can have clear parameter names and still leave its most important rules unstated. Can a withdrawal amount be zero? Must an account already be open? If a call succeeds, is the balance guaranteed to have changed, or has the request merely been accepted for later processing? When those questions are unclear, callers make assumptions. Different callers may make different assumptions, and failures appear far from the decision that caused them.

Software Engineering 05 Sep 2026 9 min read

Replacing Boolean Flag Parameters with Explicit Operations

A boolean parameter looks harmless because it carries only two values. The problem is that those values often control two different behaviours while saying almost nothing at the call site. Consider set_access(user, true). Does true mean grant access, require approval, make access permanent, or enable logging? A developer has to remember the parameter name or inspect the function before the call becomes clear. This problem becomes more expensive when the flag selects different validation, side effects, or failure rules. One function then behaves like two operations hidden behind a small parameter.

Software Engineering 05 Sep 2026 11 min read

Designing with Preconditions and Postconditions

A function can have a precise type signature and still leave its most important rules implicit. A transfer operation may accept two accounts and an amount, yet the signature does not tell you whether the amount may be zero, whether the source needs enough funds, or what must be true after a successful transfer. When these rules remain scattered through comments, conditionals, and tests, callers have to reconstruct the contract themselves. That makes misuse easier and changes harder to reason about.

Software Engineering 05 Sep 2026 9 min read

Designing Idempotent Operations for Safe Retries

A caller sends a request to create a payment. The server processes it, but the response is lost because the connection closes. The caller now has a difficult choice: retry and risk charging twice, or stop and risk leaving the payment incomplete. This is not mainly a networking problem. It is an operation-design problem. When a caller cannot tell whether an attempt succeeded, retrying is only safe when the system has a way to recognize that the new attempt represents the same intent.

Software Engineering 04 Sep 2026 10 min read

Versioning Contracts, Not Just Releases

A version number is useful only when its consumers can make a decision from it. Suppose a library changes from 2.4.1 to 2.5.0. A developer considering the upgrade wants to know something practical: can existing code keep working, or must it change? The answer does not come from how much code the maintainer edited. It comes from whether the release changed a contract that consumers depend on. That contract includes more than function names. It can include accepted inputs, returned values, error behavior, configuration, file formats, command-line options, extension points, and other observable behavior that the project promises to preserve.

Software Engineering 04 Sep 2026 7 min read

Separating Commands from Queries

A method named getBalance looks harmless. A developer may call it twice, use it while debugging, or add it to a log statement without expecting the program to change. If that method also clears pending adjustments, increments a counter, or refreshes state, those ordinary actions can alter behaviour. The underlying design problem is not simply a poor method name. Reading information and changing state have different consequences, yet one operation is doing both.

Software Engineering 04 Sep 2026 8 min read

Replacing Long Parameter Lists with Parameter Objects

A function can become difficult to call correctly even when its implementation is simple. The problem often appears as a growing parameter list: several values travel together, callers must remember their order and meaning, and the same group is passed through multiple layers. One useful refactoring is a parameter object: a small type that groups parameters which belong to one concept. The goal is not to make a function signature shorter at any cost. The goal is to give related data a name, make invalid combinations harder to create, and give future changes a natural home.