Middle · Gin services
Recovery middleware versus business rollback
Recovery catches a panic and returns 500 after a database operation. Which guarantees can you honestly report?
Try explaining this aloud before opening the answer. State your assumptions and trace a boundary case.
package main
import (
"errors"
"fmt"
"net/http/httptest"
"github.com/gin-gonic/gin"
)
var errMissing = errors.New("missing")
func main() {
gin.SetMode(gin.TestMode)
r := gin.New()
r.Use(func(c *gin.Context) {
c.Next()
if c.Writer.Written() || len(c.Errors) == 0 {
return
}
status := 500
if errors.Is(c.Errors.Last().Err, errMissing) {
status = 404
}
c.JSON(status, gin.H{"error": "request failed"})
})
r.GET("/orders/:id", func(c *gin.Context) { _ = c.Error(errMissing); c.Abort() })
w := httptest.NewRecorder()
r.ServeHTTP(w, httptest.NewRequest("GET", "/orders/7", nil))
fmt.Println(w.Code, w.Body.String())
}Reveal a strong answer
Framework recovery can keep a panic from crashing a request-serving goroutine, but it cannot undo a committed database write or decide whether a remote operation succeeded. Expected application errors should travel through a deliberate mapping policy. Assign one layer to produce the response, and test both its status and public body rather than checking only that the handler returned.
Use table-driven requests with method, path, input, verified identity and expected response. Check status, headers, JSON shape and whether forbidden side effects occurred. Inject a narrow application service and test the real storage adapter separately. Add panic recovery as a last-resort boundary, plus structured logging, without using panic as routine validation control flow. The route records an application error and stops the pending handler chain. After Next returns, middleware maps a known missing value to 404 and writes once if no earlier layer committed the response. The public body is deliberately stable and does not expose a database error. Logging the detailed cause belongs at a protected reporting boundary.
A test that asserts only status 200 can miss a leaked secret body or a second write after an error. Returning an error from a helper does not automatically attach it to Gin. Recovery is not a transaction manager. Test malformed and unauthorized requests, missing resources, dependency cancellation, unknown outcomes and a panic after a simulated effect.
Common trap
Recovery is not a transaction coordinator and an HTTP error is not evidence of no side effects.
Try it
Write an error-mapping test that checks status, public body and whether the application side effect was already committed.
Worked solution
You can report that the panic was handled at a request boundary, not that storage or external effects were rolled back. Expected errors should have a deliberate mapping and one response writer owner. Test the response body for accidental information exposure and test transaction/effect recovery separately. Neither a nil handler return nor a 500 tells the full business outcome after a partial operation.
Change the constraint
What should the client do after a write timeout whose outcome cannot be determined from the response?
How did you do?
Self-assessment, not a compiler pass or interview score. Explain the reasoning unaided before marking understood.
Not marked. Progress stays on this browser, separate from course completion.
Primary references
Checked 2026-10-28. Use documentation for your pinned runtime, driver and tool version.