Goa v3.30.0 adds design-driven retries for temporary client failures. Service authors can now declare that replaying a method with the exact same input is safe; Goa carries that contract into generated HTTP, gRPC, and JSON-RPC clients so callers do not need transport-specific retry loops.
Idempotent client retries
Mark a replay-safe method with Idempotent() and mark designed transient errors with Temporary():
Method("get_message", func() {
Idempotent()
Error("unavailable", func() {
Temporary()
})
// Payload, result, and transport mappings...
})Generated unary clients retry once, after a short jittered delay, when the failure is temporary. The retry reuses the same typed payload and caller context, so deadlines and cancellation still bound the complete operation. Existing methods are unchanged: Goa never retries a method unless its design explicitly declares Idempotent().
The two declarations express different facts:
Idempotent()promises that replaying the complete invocation has the same externally visible effect as invoking it once.Temporary()identifies a designed service error that may succeed when attempted again.
Both are required for automatic retries of designed errors. Transport failures use transport-owned classification but still require Idempotent().
Transport behavior
HTTP
Generated HTTP clients rebuild and resend eligible requests when they encounter a temporary designed error, a transient connection or response failure such as EOF, connection reset, broken pipe, temporary DNS failure, or timeout, or an HTTP status already classified as temporary (408, 409, 425, 429, 502, 503, or 504).
Generated OpenAPI 2.0, 3.0, and 3.2 operations advertise the contract with:
x-goa-idempotent: truegRPC
Generated protobuf methods declare:
option idempotency_level = IDEMPOTENT;Generated unary clients retry temporary designed errors and transport failures reported as gRPC Unavailable. Other undecoded gRPC failures remain faults; DeadlineExceeded remains a timeout and is not replayed after the caller's deadline has expired.
JSON-RPC
Generated unary JSON-RPC clients use the same endpoint-level retry contract and temporary-error classification. The service design remains the single place where replay safety is declared.
Deliberate exclusions
Goa does not automatically replay established streams, WebSocket or Server-Sent Events connections, multipart requests, or custom raw request bodies. These operations cannot be safely reconstructed from a typed payload alone and need protocol-specific recovery semantics.
Try it
The new retry example runs the same idempotent method over HTTP and gRPC. Its server intentionally fails the first invocation, while each generated client retries and returns the successful result without application-owned retry code.
Upgrade notes
- Run
go get goa.design/goa/v3@v3.30.0and regenerate clients to use automatic retries. - Add
Idempotent()only where replaying the exact invocation is genuinely safe. - No migration or coordinated server/client rollout is required. The DSL is additive, existing methods retain their behavior, and the protobuf option and OpenAPI extension do not change request or response wire formats.
Dependencies
- Updated
github.com/getkin/kin-openapifrom v0.133.0 to v0.144.0 in #3949. - Refreshed gRPC, genproto, and test dependencies across Goa, examples, and plugins.
Contributors
Thanks to @raphael for the idempotent retry design, implementation, tests, and examples.