RT
Backend TuesdaySep 1, 20267 min read

Choose a Backend Stack After Defining Failure Semantics

Define API failure behavior, data integrity, observability, and rollback before comparing TypeScript, Spring, Go, or another backend implementation.

Răzvan Todică, Senior Full-Stack Engineer and Team Lead
Răzvan Todică

Lead software engineer and technical consultant working across React, Next.js, TypeScript, Node.js, product delivery, and team leadership.

  • backend stack selection
  • api contracts
  • failure semantics
  • observability
  • rollback design
Editorial cover for Choose a Backend Stack After Defining Failure Semantics
Original editorial cover generated for this article.
On this page
  1. A technology list is not yet a decision
  2. Start with one explicit asynchronous API contract
  3. Failure behavior determines the required mechanisms
  4. Validation fails
  5. The request is repeated
  6. Scheduling fails after state is stored
  7. A worker stops mid-operation
  8. Processing fails permanently
  9. Observability should follow the contract states
  10. Make rollback a contract-preservation exercise
  11. Compare implementations only after the contract is stable
  12. Define the promise, then choose the machinery
  13. Official sources

A product team needs to implement an asynchronous API. The first conversation quickly turns to Node.js, Spring, or Go. That discussion may be premature.

Before selecting an implementation stack, the team needs to know what the endpoint promises, how it protects data, what happens when work fails, and how operators can recover it. Without those decisions, a technology comparison has no stable workload or reliability target. It becomes a comparison of preferences rather than evidence.

The supplied references establish only broad starting points. The TypeScript documentation points to starter projects that include Node.js and command-line contexts (TypeScript Playground). The Spring overview presents Spring as an option for Java development (Spring overview). The Go source is the language specification (Go specification).

Those facts identify possible ecosystems, but they do not establish how a particular service will behave under retries, partial failure, deployment, or attack. Backend stack selection should therefore follow—not replace—a precise contract and operating model.

A technology list is not yet a decision

A framework or language can only be evaluated against requirements. For a backend service, those requirements usually span five connected areas:

  • API contract: accepted inputs, responses, compatibility rules, and duplicate-request behavior.
  • Data integrity: which state transitions must be atomic and which inconsistencies are tolerable.
  • Reliability: timeout, retry, cancellation, and recovery behavior.
  • Security: authentication, authorization, validation, sensitive-data handling, and dependency boundaries.
  • Operating cost: deployment complexity, observability, incident work, maintenance, and team capability.

This is an editorial interpretation, not a claim that one of the supplied technologies handles these concerns in a particular way. The available metadata does not document runtime guarantees, security controls, queue behavior, database integration, or operational costs for any option.

That evidence gap matters. Security and runtime behavior should be checked in relevant primary documentation once the concrete implementation choices are known. A landing page, playground, or language specification alone is not enough to justify claims about an application framework, driver, queue client, or deployment environment.

Start with one explicit asynchronous API contract

Consider a hypothetical POST /exports endpoint. This is a proposed design example, not a description of any supplied technology or a production result.

Assume the endpoint accepts a resource identifier and export format, then schedules work that may finish later. A minimal contract could define:

  1. Invalid input is rejected before work is scheduled.
  2. Accepted work returns an operation identifier.
  3. Repeating the same logical request with the same idempotency key returns the existing operation rather than creating another one.
  4. Clients can query an operation as pending, running, completed, or failed.
  5. A completed operation exposes only the result the authenticated caller is authorized to access.
  6. A failed operation can be retried only according to an explicit policy.

The status names and response codes are design choices that the team must document. The important point is that duplicate handling, authorization, and terminal failure are part of the contract rather than accidental consequences of a framework.

A corresponding operation record might contain:

operation_id
request_fingerprint
principal_id
state
attempt_count
created_at
updated_at
failure_category

This is a conceptual schema. Storage types, retention, indexes, and constraints remain implementation decisions.

For data integrity, the design needs an atomic rule preventing two active operations for the same idempotency key and request fingerprint. If a queue is involved, the team must also decide what happens when the database write succeeds but message publication does not. Possible designs should be compared by their recovery behavior, not by assuming that a multi-step write is automatically atomic.

Failure behavior determines the required mechanisms

The contract becomes useful when each failure has an intended outcome.

Validation fails

No operation should be created or queued. The response should distinguish a caller-correctable request from a server-side failure without exposing internal details.

The request is repeated

The service should apply its documented idempotency rule. Returning the existing operation can prevent duplicate work, but only if the request identity and retention window are clearly defined.

Scheduling fails after state is stored

The operation must remain discoverable. A recovery process could locate unscheduled operations and try publication again. If that process does not exist, the system may retain work that no worker can see.

A worker stops mid-operation

The operation should not remain invisibly active forever. The design needs an explicit timeout, lease, or reconciliation rule. Which mechanism is appropriate depends on the chosen queue and runtime, so its behavior must be verified against their primary documentation.

Processing fails permanently

The operation should reach a terminal state with a stable failure category. Internal diagnostics can be retained for operators, while client responses avoid leaking secrets, stack traces, or infrastructure details.

These requirements provide concrete questions for evaluating an implementation. Can its libraries express the required atomic boundary? What delivery behavior does the selected queue document? How are cancellations propagated? Which failures can be retried without repeating side effects? Those questions are more discriminating than a general language comparison.

Observability should follow the contract states

Observability is most useful when it explains the same state machine exposed by the API.

For the example, logs and traces could carry the operation identifier and a safe correlation identifier. Operational measurements could count accepted operations, transitions to each terminal state, retry attempts, and operations that remain in a non-terminal state beyond the agreed threshold.

This is a proposed instrumentation model, not a claim about built-in support. Sensitive request data should not be added to telemetry by default. The team must define which fields are safe, who can access them, and how long they are retained.

An incident-recovery procedure can then be concrete:

  1. Identify operations stuck in a non-terminal state.
  2. Determine whether their side effect started or completed.
  3. Reconcile state before retrying.
  4. Retry only operations covered by the idempotency policy.
  5. Record the recovery action for later review.

The procedure deliberately avoids promising automatic recovery. Automation is justified only after the implementation can reliably distinguish safe retries from actions that may duplicate a side effect.

Make rollback a contract-preservation exercise

Rollback is not merely redeploying an older binary. A previous version may not understand a newly written state, field, or message.

For this API, the smallest reliable rollout would favor additive changes: introduce fields or states before requiring them, keep readers tolerant of the agreed transition, and avoid removing the old path until rollback no longer needs it. If a schema migration cannot be reversed safely, the deployment plan should describe forward recovery rather than call it reversible.

Again, these are design criteria. The supplied sources do not document migration tools or compatibility guarantees for the candidate options.

Compare implementations only after the contract is stable

Once the example contract and failure model are agreed, evaluate each candidate with the same questions:

  • Which primary documents support the required runtime and security behavior?
  • Can the data-integrity boundary be implemented without undocumented assumptions?
  • How are queue delivery, retries, cancellation, and shutdown defined?
  • Can operators observe every contract state and investigate transitions?
  • Can a release be rolled back without corrupting stored state or messages?
  • What deployment, maintenance, and incident-response work will the design create?
  • Does the team have the capability to operate it, or is training part of the cost?

Do not use benchmark claims unless the workload, environment, versions, and source are explicit. No benchmark evidence is supplied here, so performance cannot distinguish TypeScript, Spring, and Go in this decision.

The smallest reliable design is the one that meets the documented contract with the fewest unverified mechanisms—not necessarily the fewest files, dependencies, or lines of code. Technology selection becomes defensible when every claimed advantage maps to a requirement and primary evidence.

Define the promise, then choose the machinery

TypeScript, Spring, and Go are possible starting points in the supplied official material. The metadata does not support declaring one of them preferable for this API.

A stronger decision path is to define duplicate behavior, state transitions, authorization boundaries, failure recovery, observability, and rollback first. Then compare implementations against those exact obligations and verify runtime and security claims in the appropriate primary documentation.

If you are refining a backend contract, planning a migration, or reviewing an operationally fragile service, a focused technical audit can help turn assumptions into explicit engineering decisions.

Official sources