Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Notifications system implementation with GetStream

Tags:

getstream-io

I'm trying to implement notifications system with GS

Basically, related models are:

  • Organizations (orgs);
  • User, the member of org. Users can have administrative privilege in org;
  • Entity that admin users can create and regular users can purchase and entities also belong to orgs.

In GS there are following feeds:

  • notification:$userId-$orgId ---- re-used default feed, personal notification feed for user in certain organization
  • Org:$orgId ---- shared feed for all organization members
  • AdminEntity:$entityId ---- notifications about users actions on entity for admin users
  • UserEntity:$entityId ---- notifications about admin/system actions on entity for regular users

Let me briefly explain basic relationships between feeds:

1) Admin creates Entity:

  • push activity to Org:$orgId about this event. Activity will have user:$userId as Actor and entity:$entityId as Object.

  • subscribe his notification:$userId-$orgId to AdminEntity:$entityId

2) User joins Organization:

  • push activity to Org:$orgId about this event

  • subscribe his notification:$userId-$orgId to Org:$orgId.

3) User purchases Entity:

  • push activity to AdminEntity:$entityId about this event.

  • subscribe his notification:$userId-$orgId to UserEntity:$entityId

And so on.

The problem with described model is that in first case User that is creator of Entity will also receive notification via Org:$orgId that he has created Entity along with other Organization members. Same thing with second case.

And this is unwanted behaviour of notifications system.

Ideally, we should not notify User about his own activities in "shared" feeds, like Org:$orgId. And the question is - how to achieve this? Maybe GS feeds should be organized in different way or it can be done via Aggregation Format syntax?

Edited: as I can see, the possible solution can be:

  • get rid of all shared feeds like Org, AdminEntity, UserEntity;

  • push activities directly to notification:$userId-org$id feed regarding users roles in organization, relationships with entities etc via add_to_many call.

I am not sure if this is idiomatic solution to use with GS.

like image 896
silverthorne Avatar asked Jan 05 '23 18:01

silverthorne


1 Answers

I think your structure could be simpler than you initially designed, and be closer to what you mention at the bottom of your post. I'd also recommend you have a read through http://blog.getstream.io/best-practices-for-instagram-style-feeds/ which sounds similar to what you're wanting to do here.

For a simplified breakdown of our feed types:

  • Notification feeds are meant for the creators of content ("15 people liked your post").
  • Aggregated feeds are meant for followers ("12 posts were added").
  • Flat feeds would be great places to add the core activities ("entities" in your case?), but you can also save additional copies of those activities to other aggregated and notification feeds using the "To" field on the activity model, similar to CC'ing an email

Any feed type can follow a Flat feed, but we generally recommend that only flat feeds and aggregated feeds follow other flat feeds. Notification feeds generally stand alone, which I'll describe below.

Let's make the following assumptions:

  • There's an admin user named "Silverthorne" with an id of 12
  • Organization ID 56 is "Acme Inc"
  • Ian is a regular user with an ID of 74
  • Silverthorne prepares some Entity content that, when saved to your own database has an ID of 63

Let's create a flat feed called org where entity activities will be made, and aggregated feed called org_aggregated for aggregated data, a notification feed called notifications, a flat feed for users called timeline. If your users could see a feed of everything authored by another user (not by organization) then I'd recommend another flat feed called usercontent.

For Silverthorne to post this activity to GetStream, the activity actor would be "user:12", the verb could be "post", the object would be "entity:63"

To pseduocode this, since I don't know which SDK you're using, would be something like:

# acme inc gets created as an organization, and so its aggregated feed
# should follow the org's flat feed
acme_org_feed = getstreamClient->feed('org', '56')
acme_org_agg_feed = getstreamClient->feed('org_aggregated', '56')
acme_org_agg_feed->follow(acme_org_feed)

At this point, every entity that gets added to Acme's org flat feed will also get aggregated. You can set how these get rolled up on your GetStream dashboard.

Now, let's have Ian follow Acme:

# ian is a member of Acme Inc, so follow their entity feed
ian_feed = getstreamClient->feed('timeline', '74')
ian_feed->follow(acme_org_feed)

If Ian is a new registration, you could send an activity to Acme's notification feed like this:

acme_notif_feed = getstreamClient->feed('org_notification', '56')
acme_notif_feed->addActivity({
  'actor': 'user:74',
  'verb': 'registration',
  'object': 'user:74'
})

When you retrieve this notification feed later, it will group all verbs together, and then break down the notification activities, so your UI could report those differently if you choose.

Now let's have Silverthorne add that Entity to GetStream: # Silverthorne adds an activity to Acme Inc's entity feed # we'll "cc" the activity to the aggregated feed acme_org_feed->addActivity({ 'actor': 'user:12', 'verb': 'post', 'object': 'entity:63', 'to': ['usercontent:12'], ... any other metadata you want to track })

When this is saved, here's what GetStream would do:

  • it stores the activity in the org flat feed for Acme (org:56)
  • it saves a copy into Ian's timeline, since user:74 follows the flat feed for org:56
  • it also saves a copy to Silverthorne's feed (usercontent:12) from the 'To' field; this is entirely optional
  • it saves a copy into the aggregated feed for Acme (org_aggregated:56) since the aggregated feed follows the flat feed

When Ian logs in, your app now has several possible feeds and options to show, and this is entirely up to your app:

  • if Ian should see a timeline of everything he follows, you'd fetch the timeline:74 feed, which will have a copy of Entity #63
  • if Ian should see a list of all organizations he follows, you can fetch ian_feed->followed() to get a list of every feed that Ian follows
  • for each of those feeds Ian follows, you could fetch that org's aggregated feed for Ian to see a summary (ie, fetch org_aggregated:56 and Ian could see one new entity added in the past day if that's how you aggregate your content)

The key here is that Ian doesn't have to follow a feed in order to see the content, your app can pull any feed to display to any user.

If Ian were to 'share' or 'like' that entity that Silverthorne posted, you'd add a new activity to the 'org_notification:56' feed with 'user:74' as the Actor, 'entity:63' is the object, and verb is whatever string you want (up to 20 characters). If you want your app to see how other users interact with the org's posts, you could fetch org_notification:56 and all users could see "Ian liked entity 63", or however your UI needs to present that.

Hope that helps to clarify things and give you some additional details. Contact our support team via email if you want further help or ideas.

As a final note, I don't think I'd do $userId-$orgId ... some of our SDKs (ie, Rails and Django) will automatically try to 'enrich' those values in your database, so you'd need additional logic to attempt to split apart those values later. We recommend using a UUID for a collision-free identifier, but that's entirely up to you to control. If a user can really be part of multiple organizations, you can just have that user 'follow' the organization's feed.

like image 106
iandouglas Avatar answered Feb 08 '23 11:02

iandouglas