Home Writing Software That Is Easier to Operate
Post
Cancel

Writing Software That Is Easier to Operate

A well-written service should be understandable both in a pull request and during an incident. Naming and formatting help, but the larger decisions are about boundaries: where state changes, where dependencies enter, and where failures become visible.

Those decisions shape how much effort the next engineer needs to make a change or diagnose a production problem.

Keep business behavior separate from transport

An HTTP handler should translate a request into an application operation. It should not also contain database access, retry policy, authorization decisions, and every detail of a downstream integration.

For example, an order endpoint can parse its request, resolve the caller’s identity, and invoke an application service. The application service owns the ordering rules; a persistence adapter owns the storage interaction. This gives each part a reason to change without creating an abstraction for every line.

Keep the boundaries small enough to follow. Google’s code review guidance emphasizes design, functionality, and unnecessary complexity. A useful review asks whether the next person can explain the implementation without reconstructing the author’s entire thought process.

Treat failure behavior as part of the API

A dependency call needs more than a success path. Decide how long to wait, what the caller will see if the dependency is unavailable, and whether retrying the operation could repeat a side effect.

Consider a payment request that times out after the remote service accepts it. An immediate retry may create a second charge unless the operation has a deduplication mechanism. The engineering decision includes the idempotency key, its storage lifetime, and how the caller retrieves the original result.

Document those semantics near the code that enforces them. A timeout value alone does not explain whether a retry is safe.

Make diagnostics answer an operational question

Useful logs explain what operation failed and provide a way to connect it to a request. Useful metrics show whether users are experiencing slower or failed operations. Useful traces reveal which dependency consumed the time.

Start with the questions someone will ask:

  • Which release is serving this request?
  • Which dependency is delaying it?
  • Is the problem affecting one operation or the entire service?
  • Can an operator identify the failure without exposing credentials or private data?

Choose fields that answer those questions. Logging every payload can make an incident harder to investigate while retaining information the service does not need to keep.

Make a change easy to review and reverse

Separate a behavior change from a large rename or formatting pass. Explain the reason for the change, its operational impact, and any migration steps. Keep configuration defaults explicit so that a missing environment variable does not silently select the wrong dependency.

When a change spans a service and its database, plan the intermediate states. An additive migration followed by an application update is often easier to operate than requiring every process to switch at exactly the same moment.

Clear code reduces the amount of context an engineer must carry. Clear operational behavior reduces the amount of guessing required when the system is under pressure. Both belong in the same engineering conversation.

This post is licensed under CC BY 4.0 by the author.