MongoDB在某些時候被人詬病,說字段過於靈活,數據結構不穩定,從而被排除在外。mongodb
例如,用戶表,大部分狀況下,都是固定格式:數據庫
| id | name | age | province |
複製代碼
類型是:express
| ID | CHAR | INT | VARTXT |
複製代碼
若是使用MongoDB,由於是文檔類型,字段增長修改靈活,這就會有一個問題,若是不當心被人insert了一個數據:json
{
name: "Jerry",
age: "18",
province: "Guangdong"
}
複製代碼
你們看這個 age: "18",特別在當前NodeJS流行的環境下,WEB提交的表但都是string,就有可能出現。固然解決辦法也是有的:markdown
const mongoose = require('mongoose')
var UserSchema = mongoose.Schema({
_id: false,
name: {
type: String,
required: [true, "user name is required"]
},
age: {
type: Number
},
province: {
type: String
}
});
複製代碼
使用mongoose的好處是,不但能夠驗證,還能夠轉換類型,除此以外,還能夠更貼合面向對象的方式使用數據,因此十分推薦引入Mongoose庫。數據結構
那麼問題來了,若是同一個數據庫的使用這,不是NodeJS環境呢?你們都知道在微服務中,有可能使用Java來實現各類數據的處理。因此最好的方式就是像MYSQL那樣在數據庫裏驗證。koa
在MongoDB3.6後,支持jsonSchema的數據驗證,經過以下方式能夠配置,也能夠修改。async
//建表的時候配置
db.createCollection( <collection>, { validator: { $jsonSchema: <schema> } } )
//隨時修改
db.runCommand( { collMod: <collection>, validator:{ $jsonSchema: <schema> } } )
複製代碼
配置的方式就是上面的2個語句,很是簡單,使用的是標準JSON Schema規範。mongoose
JSON Schema規範內容,這裏不鋪開闡述,你們能夠本身搜索。固然mongoDB也有本身的一些字段類型,這一點和Javascript有點不同。例如int,那麼10是合法的,但10.1是不合法的。還有一些細則,例以下面的year。微服務
year: {
bsonType: "int",
minimum: 2017,
maximum: 3017,
description: "must be an integer in [ 2017, 3017 ] and is required"
},
複製代碼
這裏僅作介紹,具體能夠到官網去看,也不復雜。
在設置好驗證後,只要驗證不經過,就會報錯:
WriteResult({
"nInserted" : 0,
"writeError" : {
"code" : 121,
"errmsg" : "Document failed validation"
}
})
複製代碼
在NodeJS裏,能夠經過代碼來設置,只是若是使用mongoose,由於不提供mongodb connector內置對象暴露,須要咱們稍稍處理一下便可。
const MongoClient = require("mongodb").MongoClient;
const URL = 'mongodb://@127.0.0.1:27017/mydb'
function connect () {
return new Promise((resolve, reject) => {
MongoClient.connect(URL, { useNewUrlParser: true }, (err, database) => {
if (err) {
console.log(`Unable to connect to the databse: ${err}`);
reject(err)
} else {
console.log('Connected to the database');
resolve(database)
}
});
})
}
async function init () {
let connection = await connect()
let db = connection.db()
//這裏寫jsonSchema
await db.createCollection(<collection>, { validator: { $jsonSchema: <schema> } })
//這裏修改jsonSchema
await db.runCommand( { collMod: <collection>, validator:{ $jsonSchema: <schema> } } )
}
init().then(() => {})
複製代碼
咱們寫一些測試用例:
const chai = require('chai')
const { expect } = chai
const URL = 'mongodb://@127.0.0.1:27017/mydb'
const MongoClient = require("mongodb").MongoClient;
function connect () {
return new Promise((resolve, reject) => {
MongoClient.connect(URL, { useNewUrlParser: true }, (err, database) => {
if (err) {
console.log(`Unable to connect to the databse: ${err}`);
reject(err)
} else {
console.log('Connected to the database');
resolve(database)
}
});
})
}
describe('mongoose', function () {
it('createCollection', async () => {
let connection = await connect()
let db = connection.db()
await db.createCollection('students', {
validator: {
$jsonSchema: {
required: ["name"],
properties: {
name: {
bsonType: "string",
description: "must be a string and is required"
},
age: {
bsonType: "int"
},
score: {
bsonType: "number"
},
height: {
bsonType: "double"
},
address: {
bsonType: "object",
required: ["zipcode"],
properties: {
"street": { bsonType: "string" },
"zipcode": { bsonType: "string" }
}
}
}
}
}
})
})
it('jsonSchemaMod', async () => {
let connection = await connect()
let db = connection.db()
let rs = await db.command({
collMod: 'students',
validator: {
$jsonSchema: {
required: ["name"],
properties: {
name: {
bsonType: "string",
description: "must be a string and is required"
},
age: {
bsonType: "int"
},
score: {
bsonType: "number"
},
height: {
bsonType: "double"
},
address: {
bsonType: "object",
required: ["zipcode"],
properties: {
"street": { bsonType: "string" },
"zipcode": { bsonType: "string" }
}
}
}
}
}
})
expect(rs).eql({ ok: 1 })
// console.log('rs:', rs)
})
it('jsonSchemaModLess', async () => {
let connection = await connect()
let db = connection.db()
let rs = await db.command({
collMod: 'students',
validator: {
$jsonSchema: {
properties: {
name: {
bsonType: "string",
description: "must be a string and is required"
},
address: {
bsonType: "object",
properties: {
"street": { bsonType: "string" },
"zipcode": { bsonType: "string" }
}
}
}
}
}
})
expect(rs).eql({ ok: 1 })
})
it('insert', async () => {
let connection = await connect()
let db = connection.db()
let rs = await db.collection('students').insertOne({
name: 'tom',
age: 10,// 10.1 若是非整型,會失敗
score: 100, //ok
height: 180, //ok
address: {
zipcode: 'code' //zipcode empty fail
},
otherField: 'an other field'
})
expect(rs.result).include({ ok: 1 })
})
it('emptyName', async () => {
let connection = await connect()
let db = connection.db()
// 若是name是必須,則會失敗
let rs = await db.collection('students').insertOne({
age: 10
})
// console.log(rs)
await expect(rs.result).include({ ok: 1 })
})
it('found', async () => {
let connection = await connect()
let db = connection.db()
let found = await db.collection('students').find().toArray()
console.log('found:', found)
expect(found).to.be.an('array')
})
})
複製代碼
這樣,咱們就實現了相似MYSQL的數據驗證。
因此呢,之後在也不要說MongoDB數據結構不穩定啦。
官方文檔傳送門:docs.mongodb.com/manual/refe…