Saeed Ghofrani
For RecruitersArchitectureCase StudiesProjectsExperienceSkillsBlogContact
Discuss a role
For RecruitersArchitectureCase StudiesProjectsExperienceSkillsBlogContact
  1. Home
  2. /Blog
  3. /Building Real-Time Systems That Stay Maintainable

Hiring for backend ownership?

The recruiter brief has my role fit, strongest production results, resume, and direct contact details.

Open recruiter briefEmail me

Saeed Ghofrani Ivari

I build backend systems, lead delivery, and stay close to production.

Focus

NestJS, data-heavy services, real-time products, and Linux operations.

Contact

sa.ghofraniivari@gmail.comTelegram
GitHubLinkedInStack OverflowDev.toRecruiter briefPlayground
realtimerabbitmqwebsocketarchitecture

Building Real-Time Systems That Stay Maintainable

Real-time features stay healthy when transport, domain events, persistence, and delivery guarantees are treated as separate concerns.

2 min readArchitecture guideSaeed Ghofrani Ivari

From my notebook

Separate product state from the way it is delivered

Where this came from

I learned these boundaries while working on chat, VoIP, video, operator updates, and Socket.IO flows. The recurring problems were rarely the socket library itself; they were ownership, reconnect behavior, durable state, and two clients disagreeing about a session.

My short checklist

  • - Separate domain events from WebSocket or Socket.IO delivery.
  • - Define durability rules before choosing a broker or cache.
  • - Let the server own room names and subscription permissions.
  • - Log connection, room, event, and correlation identifiers.

A real-time feature usually starts as "send this event to the browser." That is fine for a prototype, but it becomes fragile when the product grows. Chat, calls, ticket updates, operator dashboards, and notification streams all need a clearer model than a socket handler with business logic inside it.

Separate the event from the transport

The domain event should describe what happened in the product: ticket assigned, message created, call started, user presence changed, payment status updated. WebSocket, Socket.IO, WebRTC signaling, push notification, and email are delivery choices. When the event is tied directly to one transport, every new delivery channel forces a rewrite.

Decide what must be durable

Not every event deserves the same guarantee. A typing indicator can disappear. A message cannot. Presence can be approximate. A billing event needs durable storage, retries, and auditability. I like to write these expectations down because they decide whether the system needs a database write, a queue, a cache entry, or only a best-effort socket emit.

  • Ephemeral events: typing, cursor movement, temporary presence.
  • Durable events: messages, support tickets, payments, critical audit records.
  • Recoverable events: notifications that can be rebuilt from persisted state.
  • Operational events: metrics and logs used to understand system health.

Keep rooms and permissions boring

Room naming, tenant boundaries, and authorization checks need to be predictable. A user should only join rooms derived from permissions the backend can verify. I avoid trusting client-provided room names. The server should decide what the connection can subscribe to and should re-check important actions when state changes.

Observe delivery instead of assuming it

Real-time bugs are often timing bugs. A dashboard looks empty because an event arrived before subscription. A call fails because two clients disagree about session state. A notification appears twice because reconnect logic resends work. Logs should include connection id, user id, room, event type, and correlation id where possible.

Maintainable real-time systems are not magical. They are normal backend systems with stricter timing expectations. The more clearly the system separates state changes from delivery mechanisms, the easier it becomes to add features without turning every socket event into a special case.

Keep transport concerns separate from durable domain events.
socket.on("message:create", async (payload) => {
  const message = await messageService.create(payload);
  await eventBus.publish("message.created", { id: message.id });
  socket.to(payload.roomId).emit("message:created", message);
});

Who may find it useful

Engineers building chat, support dashboards, calls, notifications, and live operations tools

Topics

Socket.IO roomsWebRTC signalingRabbitMQ coordinationPresence and notification patterns