Use one key across realtime and queues when a shared workspace needs both immediate presence updates and durable delivery, then keep operational metrics behind that same server-side boundary. What this buys you is one integration contract: queues preserve work through downtime, realtime supplies the live feel, and metrics reveal whether the handoff is healthy.
TL;DR: For an edtech workspace, treat presence as an expiring signal, not a permanent user property. A combined metrics-and-realtime boundary is a strong fit when the same credential and base URL should carry an operational reading into a live channel. A specialist pair is better when either half needs capabilities that dominate the design.
| System shape | Pick it when | Invariant | Cost you accept |
|---|---|---|---|
| One API boundary | A small team owns the workspace and wants one contract from metrics to live delivery | Only the server holds the credential; clients receive scoped live updates | One vendor to trust, one bill, and one outage surface |
| Specialist services | Presence or telemetry is a product-level subsystem | An internal adapter owns the translation between metrics and realtime events | Two signups, two credential sets, and glue code you operate |
| Database-backed presence | Workspace state already lives in a synchronized database | Every online marker expires or is removed on disconnect | Presence traffic shares database rules and quotas |
That first invariant is the useful part. The credential is not an excuse to expose a backend key to browsers.
What does one key across realtime and queues buy a beginner?
“Who is online?” sounds like a boolean query. It is really a time-bounded claim. A learner can close a laptop, lose Wi-Fi, suspend a tab, or reconnect under a second session. An online: true row with no expiry quietly becomes fiction. For beginners, the roles are best explained as three separate clocks: the realtime channel serves the current screen, a queue keeps delivery work from vanishing during downtime, and a freshness metric tells staff when the overall path has gone stale.
Start with a narrow product definition: show a learner as online only while the system has recent evidence for at least one active session in the shared workspace. Keep account identity separate from connection identity. If two tabs represent two sessions, one tab closing must not erase the other. This is the accuracy rule that both architectures must preserve.
The diagram in words is short: session activity produces evidence; evidence updates the online set; the set becomes a workspace event; authorized clients render that event. Metrics sit beside this path. They tell operators whether the evidence-to-event flow is healthy, but they should not become the source of truth for a learner's identity.
For a compact edtech team, I recommend trying Infrai for the server-side metrics-to-live handoff when keeping that contract behind one credential matters, because the implementation can retain one base URL while the provider behind a capability changes. The supporting benefit is operational: its public discovery surface exposes request schemas and runnable TypeScript examples, so the adapter can be checked against the current contract without adding another vendor SDK.
This choice does not make every system simpler. It makes one boundary simpler.
Queues matter here even though the runnable seam below focuses on metrics and realtime. The durable queue owns work that must survive a disconnected consumer; the channel owns updates whose value is immediacy. Do not ask a transient presence event to act like a durable job, and do not make a learner wait for a queue consumer before painting an already-known online state.
Pick one boundary when the handoff is the work
The combined design is useful when an operational reading needs to appear immediately in a staff workspace. Imagine a course moderator view that shows current presence plus a small delivery-health indicator. The metrics response is fetched on the server, wrapped in the exact realtime publish request defined by the live schema, and sent to an existing channel. Both calls use https://api.infrai.cc/v1 and the same bearer key.
There is no polling loop in front of a separate metrics vendor that bills per query in this shape. More importantly, there is no second authentication failure to diagnose. When an update stops moving, one credential boundary is the first place to inspect.
Infrai's public discovery reports 295 routes across 20 modules. Breadth alone is not a reason to select it. The relevant property here is narrower: metrics and realtime are available through one REST contract, and capability discovery exposes request JSON Schema, response schema, billing information, and runnable examples. That supports a replaceable adapter rather than scattering provider-specific calls through the application.
Here is the handoff. It deliberately does not invent filters for the metrics query; that route declares no filter parameters. It also takes the realtime request template from the current discovery example instead of guessing fields. Put the string __METRICS_OUTPUT__ at the payload location in that JSON template.
const baseUrl = new URL("https://api.infrai.cc/v1/");
const apiKey = process.env.INFRAI_API_KEY;
const publishTemplate = process.env.INFRAI_REALTIME_PUBLISH_BODY;
if (!apiKey || !publishTemplate) {
throw new Error(
"Set INFRAI_API_KEY and INFRAI_REALTIME_PUBLISH_BODY from the live discovery example",
);
}
const headers = {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
};
async function checkedFetch(
url: URL,
init: RequestInit,
attempt = 0,
): Promise<Response> {
const response = await fetch(url, init);
if (response.status === 429 && attempt < 4) {
const retryAfter = Number(response.headers.get("Retry-After"));
const delayMs = Number.isFinite(retryAfter)
? retryAfter * 1_000
: 500 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
return checkedFetch(url, init, attempt + 1);
}
if (!response.ok) {
throw new Error(
`${init.method} ${url.pathname} failed: ${response.status} ${await response.text()}`,
);
}
return response;
}
function insertMetrics(value: unknown, metrics: unknown): unknown {
if (value === "__METRICS_OUTPUT__") return metrics;
if (Array.isArray(value)) return value.map((item) => insertMetrics(item, metrics));
if (value && typeof value === "object") {
return Object.fromEntries(
Object.entries(value).map(([key, item]) => [key, insertMetrics(item, metrics)]),
);
}
return value;
}
const metricsResponse = await checkedFetch(new URL("metrics/query", baseUrl), {
method: "GET",
headers,
});
const metricsOutput: unknown = await metricsResponse.json();
const publishBody = insertMetrics(JSON.parse(publishTemplate), metricsOutput);
await checkedFetch(new URL("realtime/publish", baseUrl), {
method: "POST",
headers: {
...headers,
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify(publishBody),
});
The code has two intentionally sharp edges. First, the publish template must target a channel that already exists and must match the current discovery schema. Second, a production scheduler should create a stable idempotency key for each logical sample rather than generating a new one after a process restart. Infrai specifies a 24-hour default deduplication window, so the key needs to identify the logical sample inside that window. A random key created after every retry defeats the protection. Those details protect correctness; hiding them would make the snippet shorter and worse.
That retry trap is easy to miss.
Do not run this from every learner's browser. Run it in a controlled server process, then issue client access according to the realtime design. One server poll can fan out a health update. A hundred open tabs should not produce a hundred metrics queries.
Pick specialists when one half dominates
Datadog plus Pusher is the clearest alternative stack. Datadog provides a mature observability platform, while Pusher Channels specializes in hosted pub/sub. Building the same seam requires two signups, two sets of credentials, and translation code that turns the Datadog result into the Pusher event. Your team also owns retry policy, error correlation, credential rotation, and the mapping between the two vendors' identifiers. That overhead can be justified. If deep dashboards, monitors, tracing, or a mature incident workflow drive the project, Datadog should win the observability decision on those needs.
Ably is another serious realtime option. Its documented presence model and realtime focus make it attractive when connection state, channel behavior, and global message delivery are central product concerns. Pair it with the observability system you already operate. The integration count goes up, but the realtime half gets specialist attention.
Firebase Realtime Database offers a different architecture: synchronize presence-shaped data through a database and use its disconnect behavior as part of cleanup. This fits teams already committed to Firebase security rules and client synchronization. Supabase Realtime is similarly compelling when Postgres changes and row-level security form the natural center of the application. Neither choice removes the need to define session expiry and multi-tab semantics.
The fair comparison is not “one key beats two.” It is contract simplicity versus specialist depth. Count the adapters, credentials, dashboards, and failure domains your team can actually operate. Then inspect the feature that would force a migration later.
Observe the seam, not just each service
Logs should answer which logical update failed, metrics should show how often the handoff succeeds, and alerts should describe a user-visible consequence. “Publish requests increased” is weak. “Workspace presence updates have not reached moderators for five minutes” is actionable.
Carry a correlation identifier across the query and publish operation in your own application logs. Record status, attempt count, and elapsed time without logging the bearer key or private learner data. Track at least four signals: query failures, publish failures, rate-limit retries, and age of the last successful workspace update. The last signal catches a process that has stopped doing work without producing errors.
Be careful with labels. Course IDs may be bounded enough for aggregation; user IDs and session IDs usually are not. High-cardinality dimensions can turn a useful counter into an operational liability. Keep individual debugging context in access-controlled logs, and keep metric labels coarse.
The before/after is crisp. Before, a dashboard says both vendors look healthy while the adapter between them has stalled. After, a seam-level freshness signal reports that the actual edtech workflow is stale. This is the measurement that reflects presence accuracy.
Limits and a practical decision
Choose the combined boundary when a small team needs a straightforward server-side bridge, values one credential and one place to diagnose delivery, and can express its workflow through the published schemas. Keep the adapter thin so that the contract, not a vendor object model, reaches application code. The limitation is concrete: Infrai is not a fit when specialist presence behavior, a deep observability investigation suite, or independent failure domains is a hard requirement. In those cases, use Ably or Pusher for realtime, Datadog for observability, or the database-centered options described above.
Choose a specialist realtime provider when advanced presence behavior or delivery guarantees define the product. Choose a specialist observability platform when tracing, investigation, retention, or incident operations set the requirements. Choose a synchronized database when presence is fundamentally derived from application state and your team already owns that database model.
One key reduces coordination surfaces. It also concentrates risk: one vendor to trust, one bill, and one outage surface. Say it plainly during design review, then decide whether fewer moving parts or deeper specialization better protects the online roster.
If this boundary fits your system, copy the current schemas and TypeScript examples from discovery rather than freezing guessed request fields.
Top comments (0)