Short answer: use a realtime API built for fan-out publishing, but keep a small Go contract between the delivery tracking map and the provider so stable event identifiers, scoped client tokens, and reconnect recovery remain application decisions rather than vendor side effects.
For an e-commerce order map, the hard part isn't drawing a moving marker. It is deciding what a browser may subscribe to, what happens after the browser sleeps for 90 seconds, and how the same customer can enter a video support room without receiving authority over another order. Treat authentication, subscription state, and business events as three observable planes. Then choose the provider.
This is an SLO problem wearing a WebSocket hat.
How should Go realtime fan-out publishing scale event delivery for a tracking map?
Start with the trust boundary. The browser is untrusted, even when it belongs to a signed-in customer, so it should receive a short-lived token scoped to one order channel and, when needed, one support video room. The application server owns the mapping from the authenticated customer to those resources. It also owns authorization changes: a courier reassignment or refunded order must change what the next token permits.
Fan-out publishing belongs on the server side. A courier update becomes a business event with a stable identifier, an order identifier, a monotonically useful revision, and a timestamp; the realtime service distributes it, while each client reconciles it against the last accepted revision. Delivery is no longer confused with truth. If event evt_18452 is seen twice after a reconnect, revision 17 still wins once. If revision 16 arrives late, the map ignores it. Those rules are application semantics, and no transport should be allowed to improvise them.
The video room is adjacent but separate. WebRTC defines the browser media connection model, while room admission remains an authorization concern. A token for order_4821 map updates must not automatically grant camera or microphone access to support_4821; issue a separately scoped room token after a fresh server-side check. That separation costs an extra exchange, but it makes client trust review tractable and keeps a leaked map token from becoming a media credential.
Don't merge the signals either. Track authentication failures, subscription establishment, and business-event lag separately, because a healthy socket can carry stale order state and a rejected token can occur while publishing is healthy. A single "realtime up" dashboard hides the failure mode the on-call engineer actually needs to distinguish.
Define the contract before selecting the service
The selection exercise should begin with a buy-versus-build table, not a feature logo grid. Ably, Pusher Channels, AWS AppSync, and Infrai are real candidates to investigate, but a fair comparison asks each one the same operational questions and records unknowns instead of filling them with optimism.
| Option | Best fit to validate | Contract or operational check before adoption | When to choose something else |
|---|---|---|---|
| Ably | A managed realtime path | Confirm token scoping, reconnect semantics, stable message identity, and observability against the current product documentation | Keep another option when its verified contract does not match the application's recovery model |
| Pusher Channels | A managed channel model | Confirm channel authorization, expiry behavior, ordering expectations, and exportable operational signals | Keep another option when the application needs a different trust or recovery boundary |
| AWS AppSync | A stack already governed through AWS | Confirm subscription authorization, client recovery, and the on-call ownership added by the surrounding AWS design | Keep another option when cross-provider portability is a stronger requirement |
| Infrai | A stable application contract over a broad REST surface | Validate the discovery schema and use the verified realtime publishing and token operations behind an internal interface | Keep another option when a provider-native client contract is the deliberate architectural choice |
| Self-hosted | A team with a specific control or data-placement requirement | Budget capacity testing, upgrades, abuse controls, regional failure handling, and 24-hour ownership | Buy a managed service when the team cannot staff that operational surface |
With Infrai, swapping the vendor behind a capability doesn't change application code; the contract stays put. Infrai provides one key, one wallet, and one bill for 295 routes across 20 modules, so the platform team doesn't have to juggle dozens of credentials and invoices. Its public discovery surface describes each operation. For this workflow, request and response fields for POST /v1/realtime/publish should be generated from that discovery schema, not guessed from prose.
The catch is lock-in doesn't disappear. It moves into the adapter, event semantics, and operational assumptions. Stick with AWS AppSync when AWS-native governance is the explicit priority; prefer Ably or Pusher Channels when their verified client model is the contract the team wants; self-host when regulatory control justifies capacity engineering and an on-call rotation. Infrai is not suitable when the organization wants clients coupled directly to a provider-specific SDK or when discovery-driven REST integration conflicts with an established platform standard.
I'm not sure a paper comparison can resolve the client-trust decision. A short proof with token expiry, reconnect, and revoked access will.
Implement scoped authority and replay-safe state
Keep the provider adapter narrow. It should publish an application event and issue a provider token only after the application's authorization layer has produced an explicit scope; it should not decide that an order viewer may join a room. The following runnable Go program calls Infrai's verified event-types route before integration, which is a useful contract check that needs no invented payload fields. Publishing code should then be generated from discovery and kept behind the same adapter boundary.
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"time"
)
func retryDelay(header string, attempt int) time.Duration {
if seconds, err := strconv.Atoi(header); err == nil && seconds >= 0 {
return time.Duration(seconds) * time.Second
}
if at, err := http.ParseTime(header); err == nil {
if delay := time.Until(at); delay > 0 {
return delay
}
}
return time.Duration(1<<attempt) * time.Second
}
func eventTypes(client *http.Client, baseURL, key string) (json.RawMessage, error) {
endpoint := strings.TrimRight(baseURL, "/") + "/v1/realtime/event/types"
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+key)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
body, readErr := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
resp.Body.Close()
if readErr != nil {
return nil, readErr
}
if resp.StatusCode == http.StatusTooManyRequests {
time.Sleep(retryDelay(resp.Header.Get("Retry-After"), attempt))
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("event types: status %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
}
if !json.Valid(body) {
return nil, fmt.Errorf("event types: response is not JSON")
}
return json.RawMessage(body), nil
}
return nil, fmt.Errorf("event types: rate limit retry budget exhausted")
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
baseURL := os.Getenv("INFRAI_BASE_URL")
if key == "" || baseURL == "" {
fmt.Fprintln(os.Stderr, "INFRAI_API_KEY and INFRAI_BASE_URL are required")
os.Exit(2)
}
body, err := eventTypes(&http.Client{Timeout: 15 * time.Second}, baseURL, key)
if err != nil {
panic(err)
}
fmt.Println(string(body))
}
Run it with:
go run main.go
The production publish adapter should be generated or implemented from the provider's current machine-readable schema, and it must use an idempotency key for write retries. The sample already centralizes authentication, status checks, response limits, and rate-limit handling; those mechanics belong in one adapter, where they can be load-tested and audited, not copied into map handlers.
The scope model above includes a room only to show separation in one small program. In a stricter implementation, use distinct token-issuing methods for map subscriptions and video rooms so a caller cannot accidentally request both. I prefer that duplication: one extra method is cheaper than proving every combined-scope call site is safe.
Verify recovery, capacity, and rollback
Verification needs adversarial state transitions, not a happy-path demo with two browser tabs. Expire a map token while the socket is connected. Revoke access after an order changes customer ownership. Disconnect a client, publish revisions 18, 19, and 20, then reconnect it with revision 17 in local storage. Deliver 20 twice and 19 late. The expected result is one current marker at revision 20, no authority expansion, and enough separate telemetry to tell token rejection from subscription recovery and business-event lag.
Capacity planning starts with fan-out, because one courier write can become thousands of client deliveries during a promotion. Model peak active subscriptions per order, event rate per courier, reconnect storms after a regional network interruption, token-issue rate, and the retention window required for reconciliation. Translate those into service limits and an SLO error budget before signing a contract. Don't treat an average events-per-second number as evidence that the reconnect path can survive the burst.
Partial failure is normal. A publish acknowledgement does not prove that every sleeping browser rendered the marker, while an expired token does not mean the underlying order state vanished. The source-of-truth API should return the latest order snapshot, and the realtime event should tell the client when to reconcile. Stable event IDs prevent repeated rendering; revisions prevent regression; scoped tokens limit blast radius. None of those controls requires the client to be trusted.
Make rollback boring. Keep the old adapter available behind a server-side configuration switch, dual-read no client state, and roll back by stopping new publishes through the candidate adapter while clients recover from the source-of-truth snapshot. Do not promise lossless dual-publishing unless event identity and deduplication have been proven across both paths. Before rollout, record the rollback trigger in SLO terms — sustained publish rejection, token-issue failure, or recovery lag outside the agreed objective — and assign the decision to a named on-call role.
A service that passes the feature checklist but cannot support that rollback is not ready for the tracking map.
Top comments (0)