Best practice for subscribe and publish architecture for chat-like app

Viewed 255

I'd like to know what best practices exist around subscribing to changes and publishing them to the user. This is a pretty broad and vaguely worded question. Therefore, allow me to elaborate on this using an example.

Imagine the following (simplified) chat-like application:

  • The user opens the app and sees the home screen.
  • On this home screen a list of chat-groups is fetched and displayed.
  • Each chat-group has a list of users (members).
  • The user can view this list of members.
  • Each user/member has at least a first name available.
  • The user can change its name in the settings.
  • And now the important part: When this name is changed, every user that is viewing the list of members, should see the name change in real-time.

My question concerns the very last point.

Let's create some very naive pseudo-code to simulate such a thing.

The client should at least subscribe to something. So we could write something like this:

subscribeToEvent("userChanged")

The backend should on its part, publish to this event with the right data. So something like this:

publishDataForEvent("userChanged", { userId: "9", name: "newname" } )

Of course there is a problem with this code. The subscribed user now gets all events for every user. Instead it should only receive events for users it is interested in (namely the list of members it is currently viewing).

Now that is the issue I want to know more about. I could think of a few solutions:

Method 1 The client subscribes to the event, and sends with it, the id of the group he is currently viewing. Like so for example:

subscribeToEvent("userChanged", { groupId: "abc" })

Consequently, on the backend, when a user changes its name, the following should happen:

  1. Fetch all group ids of the user
  2. Send out the event using those group ids

Something like this:

publishDataForEvent("userChanged", { userId: "9", name: "newname" }, { groupIds: ["abc", "def" })

Since the user is subscribed to a group with id "abc" and the backend publishes to several groups, including "abc", the user will receive the event.

A drawback of this method is that the backend should always fetch all group ids of the user that is being changed.

Method 2

Same as method 1. But instead of using groupIds, we will use userIds.

subscribeToEvent("userChanged", { myUserId: "1" })

Consequently, on the backend, when a user changes its name, the following should happen:

  1. Fetch all the user ids that relate to the user (so e.g. friendIds based on the users he shares a group with)
  2. Send out the event using those friendIds

Something like this:

publishDataForEvent("userChanged", { userId: "xyz", name: "newname" }, { friendIds: ["1", "2" })

An advantage of this is that the subscription can be somewhat more easily reused. Ergo the user does not need to start a separate subscription for each group he opens, since he is using his own userId instead of the groupId.

Drawback of this method is that it (like with method 1 but probably even worse) potentially requires a lot of ids to publish the event to.

Method 3

This one is just a little different.

In this method the client subscribes on multiple ids.

An example:

On the client side the application gathers all users that are relevant to the current user. For example, that can be done by gathering all the user ids of the currently viewed group.

subscribeToEvent("userChanged", { friendIds: ["9", "10"] })

At the backend the publish method can be fairly simple like so:

publishDataForEvent("userChanged", { userId: "9", name: "newname" }, { userId: "9" } )

Since the client is subscribed to user with userId "9", amongst several users, the client will receive this event.

Advantage of this method is that the backend publish method can be held fairly simple.

Drawback of this is that the client needs quite some logic to subscribe to the right users.


I hope that the examples made the question more clear. I have the feeling I am missing something here. Like, major chat-app companies, can't be doing it one of these ways right? I'd love to hear your opinion about this.


On a side note, I am using graphql as a backend. But I think this question is general enough to not let that play a role.

1 Answers
  • The user can change its name in the settings.
  • And now the important part: When this name is changed, every user that is viewing the list of members, should see the name change in real-time.

I assume the user can change his name via a FORM. The contents of that form will be send with a HTTP-Reuqest to a backand script that will do the change in a DB like

update <table> set field=? where userid=? 

Preferred

This would be the point where that backend script would connect to your web socket server and send a message like.

{ opcode:'broadcast', task:'namechange', newname='otto' ,userid='47111' }

The server will the broadcast to all connected clients

{task:'namechange', newname='otto' ,userid='4711' }

All clients that have a relationship to userid='4711' can now take action.

Alternative 1

If you cant connect your backend script to the web socket server the client might send { opcode:'broadcast', task:'namechange', newname='otto' ,userid='47111' } right before the FORM is trasnmitted to the backend script.

This is shaky because if anything goes wrong in the backend, the false message is already delivered, or the client might die before the message goes out, then no one will notice the change.

Related