天天看點

在MongoDB中處理"使用者-角色"關系

假設我在設計一個有 使用者-角色 關系的系統

受長久以來的關系資料庫的經驗影響,一開始我的設計是這樣的.

const UserSchema = new Schema(
  {
    username: { type: String, require: true, unique: true },
    password: { type: String, require: true },
    nickName: String,
  },
);
UserSchema.virtual('roles', {
  ref: 'UserRole',
  localField: '_id',
  foreignField: 'user',
});

//UserSchema中定義了一個roles的虛拟鍵,
//收集UserRole集合中字段user與之相等的記錄,
//即得到所有的角色

const UserRoleSchema = new Schema(
  {
    user: { type: Schema.Types.ObjectId, ref: 'User' },
    role: { type: Schema.Types.String, index: true, require: true },
    roleData: { type: Schema.Types.ObjectId, refPath: 'role' },
    createTime: { type: Date, default: new Date() },
    status: Number,
  }
);

//UserRole中的Role是一個字元串,
//其值為類似Admin,Teacher,Student這樣的具體類型
//這些具體角色,即意味着有單獨的集合存儲
//roleData即指向那些具體的集合中的記錄
//通過refPath指向role的值,一種動态的引用

const AdminSchema = new Schema(
  {
    user: { type: 'ObjectId', ref: 'User' },
    adminProp:String
  },
);

//Admin中就是存放與這類型角色有關的字段,
           

我的思路是盡量通過引用和populate方法來實作自動的資料引用更新. 比如我如果想移除一個使用者的角色,我隻需要删除UserRole集合中對應資料,在userModel.populate('roles')的時候就會得到更新後的結果.

另外,因為不同角色類型,字段不一樣,我又希望使用明确鍵定義的Schema(友善資料校驗).是以在UserRole中通過refPath做了動态引用.

雖然上邊的設計能正常工作,不過呢,确實産生了好幾個不同的集合.User,UserRole,Admin,Teacher,Student...

如果不考慮'強Schema',按照mongodb的設計理念來說,總感覺象我這裡的使用者與角色應該用嵌入的方式直接塞到User下邊

調整如下

const UserSchema = new Schema(
  {
    username: { type: String, require: true, unique: true },
    password: { type: String, require: true },
    nickName: String,
    roles: { type: [UserRoleSchema], default: [] },
  },
}
//直接定義成數組子文檔嵌套的方式

const UserRoleSchema = new Schema(
  {
    role: { type: Schema.Types.String, index: true, require: true },
    roleData: Schema.Types.Mixed, 
    createTime: { type: Date, default: new Date() },
    status: Number,
  }
}
//UserRoleSchema這個子文檔定義裡,
//去掉了原本用于關聯User集合的user字段,
//roleData也從原來的引用到具體角色的類型變成了混合類,
//以友善直接插入各種類型角色資料

const AdminSchema = new Schema(
  {
    adminProp:String
  },
  { _id: false },
);

//Admin定義中隻是注意省掉沒意義的_id           

是以最後的一個使用者文檔變成

{
  "_id" : ObjectId("5de87d8de6399413683b6c13"),
  "createTime" : ISODate("2019-12-05T03:45:27.731Z"),
  "username" : "user_test",
  "password" : "pass_test",
  "roles" : [{
      "_id" : ObjectId("5de8a3b448dc3530c8ebc64e"),
      "role" : "Admin",
      "roleData" : {
          "adminProp":"test",
      },
      "createTime" : ISODate("2019-12-05T06:29:07.465Z")
    }]
}           

對比前後兩種方式.前者用到了引用,後者用的是嵌套.象這種使用者-角色的話,用嵌套更适合.畢竟一個使用者的角色資料通常都不多.

這樣設計更符合MongoDB的MODEL設計思想.而且最後就隻需要一個USER集合,也沒有populate vituals.更簡潔了些

不知道大家怎麼看?

最後參考一下這個連結

https://coderwall.com/p/px3c7g/mongodb-schema-design-embedded-vs-references