Ivan Drinchev
Ivan Drinchev

Reputation: 19581

Mongoose / MongoDB User notifications scheme suggestions

I was wondering what is the best scheme for user/notifications kind of scenario like the following :

Option One

Embedded notifications scheme

Notifications = new Schema ( {
    message : String,
    date : { type : Date, default: Date.now() }
    read : { type: Boolean, default : false }
});

User = new Schema( {
    username : String,
    name : String,
    notifications : [Notifications]
});

Pros :

Cons :

Option Two

Populate (DBRef like) objects

Notification = new Schema ({
    message : String,
    date : { type : Date, default : Date.now() }
});

UserNotification = new Schema ({
    user : { type : Schema.ObjectId, ref : 'User' },
    notification : { type : Schema.ObjectId, ref : 'Notification' },
    read : { type : Boolean, default : false }
});

User = new Schema( {
    username : String,
    name : String,
    notifications : [ { type : Schema.ObjectID, ref : 'UserNotification' } ]
});

Pros :

Cons :

Questions

  1. What do you think is the best scheme from those two?
  2. Am I missing something or some kind of basic NoSQL knowledge?
  3. Can someone propose better scheme?

Thank you, in advance and I'm sorry for the long post, but I think I can't explain it simpler.

Upvotes: 24

Views: 9628

Answers (2)

Faizal Pribadi
Faizal Pribadi

Reputation: 412

// namespace like a channel
// we have a notification from specific channel or namespace
const NamespaceSchema = new Schema({
  name: {
    type: String,
    unique: true,
    required: true
  },
  author: {
    type: String,
    required: true
  },
  createdAt: {
    type: Boolean,
    default: Date.now()
  },
  notifications: [{
    type: Schema.Types.ObjectId,
    ref: 'Notification',
  }]
});

// the notification schema have a subscribers to specific notification (objectId)
//
const NotificationSchema = new Schema({
  title: {
    type: String
  },
  message: {
    type: String
  },
  read: {
    type: Boolean,
    default: false
  },
  subscribers: [{
    type: Schema.Types.ObjectId,
    ref: 'Subscriber'
  }],
  createdAt: {
    type: Date,
    default: Date.now()
  }
});
// subscribers subscribe to a namespace or channel
const SubscriberSchema = new Schema({
  subscriber: {
    type: String,
    required: true
  },
  namespaces: [{
    type: Schema.Types.ObjectId,
    ref: 'Namespace',
  }]
});

Upvotes: 0

mpobrien
mpobrien

Reputation: 4962

Option 1 looks like it will probably result in a lot of excessive document growth and moves, which would be bad for performance, since most of your writes will be going to the embedded doc (Notifications).

Option 2 I'm not totally clear on your strategy - it seems redundant to have those 3 collections but also embed a list of notifications by objectId if you are already referencing user by ID in the notifications table. You could index on user in Notifications table, and then eliminate the nested array in the Users table.

(EDIT) Here's another strategy to consider.

Three collections that look like this:

Users:
   _id: objectid
   username : string
   name: string

Notifications:
   _id:  objectid
   to (indexed):   objectid referencing _id in "users" collection
   read: boolean

Global Notifications:
   _id: objectid
   read_by: [objectid referencing _id in users]

For notifications meant for a single user, insert into Notifications for that user. For multiple users, insert one for each user (alternately, you could make the "to" field an array and store _ids of all the recipients, and then maintain another list of all the recipients who have read it). To send a notification to all users, insert into Global Notifications collection. When the user reads it, add their user's _id to the read_by field. So, to get a list of all the unread notifications of a user, you do two queries: one to notifications, and one to global notifications.

Upvotes: 16

Related Questions