ODUONYE. / ENGINEERING NOTES
← All notes

Designing a Reliable Go Backend for AI and Real-Time Products

31 August 2026 · Daniel Oduonye

An AI product still needs a dependable backend. The model may produce the interesting output, but the backend owns the request, state, permissions, retries, events, and final response.

For AI and real-time products, I like a simple design: keep the request path clear, move slow work to a worker, store visible state, and make every external dependency fail in a controlled way.

Give each part one job

A small Go service can start with a few clear boundaries:

HTTP API -> application service -> durable state
                    |
                    -> queue -> worker -> model or external tool
                    |
                    -> event stream -> connected clients

The HTTP layer should validate input, authenticate the request, and return a useful status. The application service should apply product rules. The worker should own slow or retryable work. The data store should make the current state visible to both the worker and the user.

This shape is easier to test than a handler that talks directly to a model, a queue, and several databases. It also gives each failure a clear home.

Put slow work behind a queue

Model calls, document processing, media generation, and third-party APIs can all take longer than a user should wait on one HTTP connection. A queue lets the API accept the job and lets the worker process it with its own timeout and retry policy.

The job record should answer:

“The AI failed” is not enough. “The retrieval service timed out after two attempts” gives the team a next step.

Make side effects idempotent

Queues can deliver the same message more than once. Network calls can time out after the remote service has already accepted the request. A retry that ignores this can publish two assets, create two sessions, or charge twice.

Use an idempotency key for every operation with a side effect. Store the key with the result and return the existing result when the same job is received again.

request ID + operation name -> idempotency key -> one stored result

Idempotency is not only a database trick. It should also be part of the contract with external services when they support it. If a provider does not support it, add a local state transition and a review path so the system does not blindly repeat an uncertain action.

Bound every dependency

Each external call needs a timeout. Each retry needs a limit and backoff. A worker should be able to stop without losing the reason it stopped.

A safe retry path usually has:

Do not retry invalid input, a permission failure, or an unsafe model response as if it were a temporary network error. Different failures need different next actions.

Treat observability as part of the API

Logs, metrics, and traces should help answer product questions, not only infrastructure questions. For an AI workflow, useful signals include request and job duration, queue depth and age, model and tool latency, retry count, validation failures, estimated provider cost, and successful, failed, and handed-off jobs.

Connect the request ID, job ID, and trace ID across the API, queue, worker, model call, and database. When a user reports a bad result, the team should be able to follow the path without guessing which version ran.

Keep real-time updates boring

Real-time products need a reliable state model before they need a clever transport. Send events with an ID, a type, and the current resource version. Let clients reconnect and ask for events after the last ID they received.

That gives the server a way to recover from a dropped connection. It also prevents a client from showing an old “processing” state after the job has already completed.

For matchmaking, collaboration, and live workflow products, define what happens when a client disconnects, reconnects, sends the same action twice, or receives events out of order.

Ship a clear container

Go is a good fit for small production services because a compiled binary is easy to package and run. The container still needs a small, pinned base image, environment configuration, secret-store integration, a useful health endpoint, graceful shutdown, structured logs, and a non-root runtime where the platform allows it.

Kubernetes provides a useful delivery layer when the service needs rolling updates, worker scaling, health checks, or separate API and worker processes. Keep the deployment boring: immutable images, explicit resources, and a rollback path.

Put security at the boundaries

Validate input before it reaches a model or tool. Authenticate webhooks. Apply rate limits. Keep projects and tenants separated in every query. Do not let text from a document, model response, or external page override the application’s real rules.

The same boundaries matter for agent systems. My Claude Code Slack project uses session persistence, project boundaries, webhook authentication, rate limits, and audit logging for this reason.

A practical Go backend checklist

  1. Is the request path separate from slow work?
  2. Does every job have visible durable state?
  3. Can a retry repeat a side effect?
  4. Are timeouts, backoff, and permanent failures explicit?
  5. Can one trace follow the request end to end?
  6. Can real-time clients reconnect without losing state?
  7. Can the container shut down without dropping work?
  8. Is there a safe human path for unclear or risky outcomes?

The model is only one dependency. A reliable AI product is the whole system around it.

See the Pineapple Go backend, read more about reliable AI workflows, and explore my selected work. You can also find me on GitHub, Hugging Face, LinkedIn, Upwork, and X, or return to oduonye.com.