TL;DR Khắc phục nhanh
Lỗi MongoServerError: Document failed validation có nghĩa là dữ liệu bạn đang cố gắng chèn hoặc cập nhật không đáp ứng các quy tắc xác thực schema được định nghĩa cho collection MongoDB của bạn. Cách nhanh nhất để khắc phục điều này là:
- Kiểm tra tài liệu bạn đang cố gắng chèn/cập nhật.
- Kiểm tra các quy tắc xác thực của collection bằng cách sử dụng
db.getCollectionInfos({ name: 'yourCollectionName' }). - Điều chỉnh dữ liệu của bạn để tuân thủ các quy tắc, hoặc (nếu phù hợp) sửa đổi các quy tắc xác thực của collection.
Nguyên nhân gốc rễ chi tiết
MongoDB đã giới thiệu xác thực schema bắt đầu từ phiên bản 3.6. Tính năng này cho phép bạn áp đặt các ràng buộc về cấu trúc và kiểu dữ liệu lên các tài liệu trong một collection. Khi bạn thực hiện thao tác insert hoặc update, MongoDB sẽ kiểm tra tài liệu đến dựa trên các quy tắc được định nghĩa trước này.
Nếu tài liệu không đáp ứng bất kỳ phần nào của quy tắc xác thực (ví dụ: thiếu trường bắt buộc, trường có kiểu dữ liệu sai, giá trị nằm ngoài phạm vi cho phép, hoặc có một trường bổ sung không được phép), MongoDB sẽ từ chối thao tác và ném ra lỗi MongoServerError: Document failed validation.
Lỗi này là một điều tốt! Nó có nghĩa là cơ sở dữ liệu của bạn đang chủ động bảo vệ tính toàn vẹn của dữ liệu theo các quy tắc mà bạn (hoặc một nhà phát triển khác) đã thiết lập. Nhiệm vụ của bạn là hiểu tại sao tài liệu bị coi là không hợp lệ và đưa nó về trạng thái tuân thủ.
Các phương pháp khắc phục
Phương pháp 1: Kiểm tra dữ liệu đang được chèn hoặc cập nhật
Bước đầu tiên luôn là xem xét tài liệu chính xác đã gây ra lỗi. Thông thường, vấn đề nằm ở một lỗi đánh máy đơn giản, một trường bị thiếu hoặc kiểu dữ liệu không chính xác trong tải trọng dữ liệu của ứng dụng của bạn.
Ví dụ về dữ liệu có vấn đề:
Giả sử collection của bạn mong đợi một trường name (chuỗi), age (số nguyên) và email (chuỗi, bắt buộc). Bạn cố gắng chèn:
{
"username": "johndoe",
"age": "twenty-five",
"email": "john.doe@example.com"
}
Ở đây, username có thể không được mong đợi, age là một chuỗi thay vì số nguyên, và trường name bắt buộc bị thiếu. Bất kỳ điều nào trong số này đều có thể khiến việc xác thực thất bại.
Quay lại mã ứng dụng của bạn và kiểm tra đối tượng hoặc từ điển bạn đang truyền cho các phương thức insertOne, insertMany, updateOne, hoặc updateMany của driver MongoDB. In nó ra console hoặc trình gỡ lỗi để xem cấu trúc và giá trị chính xác của nó.
Phương pháp 2: Xem lại các quy tắc xác thực Schema của Collection
Khi bạn đã biết dữ liệu mình đang gửi là gì, bạn cần biết nó đang được so sánh với những quy tắc nào. Bạn có thể truy xuất các quy tắc xác thực của một collection bằng lệnh db.getCollectionInfos() trong mongosh:
db.getCollectionInfos({ name: 'yourCollectionName' });
Thao tác này sẽ trả về một mảng thông tin về collection của bạn. Hãy tìm trường options.validator. Dưới đây là ví dụ về những gì bạn có thể thấy:
[
{
"name": "yourCollectionName",
"type": "collection",
"options": {
"validator": {
"$jsonSchema": {
"bsonType": "object",
"required": ["name", "email", "age"],
"properties": {
"name": {
"bsonType": "string",
"description": "Must be a string and is required"
},
"email": {
"bsonType": "string",
"pattern": "^.+@.+\\..+$",
"description": "Must be a valid email address and is required"
},
"age": {
"bsonType": "int",
"minimum": 18,
"description": "Must be an integer >= 18 and is required"
},
"status": {
"enum": ["active", "inactive", "pending"],
"description": "Can only be one of the enum values"
}
},
"additionalProperties": false
}
},
"validationAction": "error",
"validationLevel": "strict"
}
}
]
Trong ví dụ này:
required: ["name", "email", "age"]: Các trường này phải có mặt.bsonType: "string"chonamevàemail,bsonType: "int"choage: Kiểu dữ liệu được áp dụng.minimum: 18choage: Giá trị phải tối thiểu là 18.pattern: "^.+@.+\\..+$"choemail: Một biểu thức chính quy cho định dạng email.enum: ["active", "inactive", "pending"]chostatus: Giá trị phải là một trong các giá trị này.additionalProperties: false: Điều này rất quan trọng! Nó có nghĩa là không có trường nào khác ngoài những trường được liệt kê rõ ràng trongpropertiesđược phép.
So sánh tài liệu của bạn (từ Phương pháp 1) với các quy tắc này. Bạn sẽ có thể phát hiện ra sự khác biệt ngay lập tức.
Phương pháp 3: Điều chỉnh dữ liệu để khớp với Schema
Khi bạn đã xác định được sự không phù hợp, hãy sửa đổi dữ liệu của ứng dụng trước khi gửi nó đến MongoDB. Đây thường là giải pháp được ưu tiên, vì nó duy trì tính toàn vẹn của dữ liệu.
Sử dụng dữ liệu có vấn đề và schema từ Phương pháp 2 của chúng ta, đây là cách để sửa chữa nó:
{
"name": "John Doe", // Đã thêm trường 'name'
"age": 25, // Đã thay đổi 'age' thành số nguyên, không phải chuỗi
"email": "john.doe@example.com" // Email đã đúng
// Đã xóa 'username' vì 'additionalProperties: false' trong schema
}
Bây giờ, nếu bạn cố gắng chèn tài liệu đã sửa này, nó sẽ vượt qua xác thực:
db.yourCollectionName.insertOne({
"name": "John Doe",
"age": 25,
"email": "john.doe@example.com"
});
Phương pháp 4: Sửa đổi các quy tắc xác thực Schema (Nếu cần)
Đôi khi, các quy tắc xác thực có thể không chính xác, quá nghiêm ngặt hoặc lỗi thời đối với logic ứng dụng hiện tại của bạn. Trong những trường hợp như vậy, bạn có thể cần sửa đổi trình xác thực của collection. Việc này cần được thực hiện cẩn thận, vì nó ảnh hưởng đến tất cả các thao tác sau này trên collection.
Bạn có thể sử dụng lệnh collMod để thay đổi các quy tắc xác thực:
db.runCommand({
collMod: 'yourCollectionName',
validator: {
// Các quy tắc xác thực mới hoặc đã sửa đổi của bạn tại đây
// Ví dụ, để cho phép trường 'username':
"$jsonSchema": {
"bsonType": "object",
"required": ["name", "email", "age"],
"properties": {
"name": { "bsonType": "string" },
"email": { "bsonType": "string", "pattern": "^.+@.+\\..+$" },
"age": { "bsonType": "int", "minimum": 18 },
"username": { "bsonType": "string", "minLength": 3 } // Trường mới được phép
},
"additionalProperties": false // Vẫn không có các trường tùy ý khác
}
},
validationAction: 'error', // 'error' (mặc định) hoặc 'warn'
validationLevel: 'strict' // 'strict' (mặc định) hoặc 'moderate'
});
validationAction: Xác định điều gì xảy ra khi xác thực thất bại.'error'từ chối thao tác,'warn'ghi lại cảnh báo nhưng cho phép thao tác.validationLevel: Xác định thao tác nào phải tuân theo xác thực.'strict'áp dụng cho tất cả các thao tác chèn và cập nhật.'moderate'áp dụng cho các thao tác chèn và cập nhật trên các tài liệu hợp lệ hiện có (các thao tác cập nhật trên các tài liệu không hợp lệ hiện có sẽ không được xác thực).
Để vô hiệu hóa hoàn toàn xác thực cho một collection, bạn có thể đặt validator thành một đối tượng trống:
db.runCommand({
collMod: 'yourCollectionName',
validator: {},
validationAction: 'warn'
});
Điều này thường không được khuyến nghị cho môi trường sản xuất trừ khi bạn có xác thực mạnh mẽ ở cấp ứng dụng.
Phương pháp 5: Xử lý lỗi xác thực trong mã ứng dụng
Trong các ứng dụng mạnh mẽ, bạn nên dự đoán các lỗi xác thực và xử lý chúng một cách khéo léo. Hầu hết các driver MongoDB sẽ ném ra một ngoại lệ hoặc trả về một đối tượng lỗi mà bạn có thể bắt.
Ví dụ (mã giả Node.js/JavaScript):
const { MongoClient } = require('mongodb');
async function insertUser(userData) {
const client = new MongoClient('mongodb://localhost:27017');
try {
await client.connect();
const database = client.db('mydb');
const users = database.collection('users');
const result = await users.insertOne(userData);
console.log(`Successfully inserted user with _id: ${result.insertedId}`);
} catch (error) {
if (error.name === 'MongoServerError' && error.code === 121) { // 121 là mã lỗi cho DocumentValidationFailure
console.error('Document failed validation:', error.message);
console.error('Problematic document:', JSON.stringify(userData, null, 2));
// Ghi lại các lỗi xác thực cụ thể nếu có sẵn trong chi tiết lỗi
// (Thông báo lỗi của MongoDB đôi khi có thể bao gồm chi tiết về quy tắc nào đã thất bại)
} else {
console.error('An unexpected error occurred:', error);
}
} finally {
await client.close();
}
}
// Example usage with invalid data:
insertUser({
username: 'testuser',
age: 'twenty',
email: 'invalid'
});
Bằng cách bắt MongoServerError cụ thể và kiểm tra các chi tiết của nó (đặc biệt là error.message hoặc có thể error.errInfo.details), bạn có thể cung cấp phản hồi tốt hơn cho người dùng hoặc ghi lại thông tin chẩn đoán cụ thể hơn.
Phòng ngừa
-
Xác thực cấp ứng dụng: Triển khai xác thực mạnh mẽ ở lớp ứng dụng của bạn trước khi gửi dữ liệu đến MongoDB. Điều này cung cấp phản hồi ngay lập tức cho người dùng và giảm các lệnh gọi cơ sở dữ liệu không cần thiết. Các thư viện như Joi, Yup (cho Node.js), Pydantic (cho Python), hoặc Hibernate Validator (cho Java) rất tuyệt vời cho việc này.
-
Tài liệu Schema rõ ràng: Ghi lại rõ ràng các schema collection MongoDB và các quy tắc xác thực của bạn cho tất cả các nhà phát triển làm việc trong dự án.
-
Kiểm thử tự động: Viết các bài kiểm thử đơn vị và tích hợp đặc biệt kiểm tra các mô hình dữ liệu của bạn dựa trên các quy tắc xác thực schema MongoDB.
-
Sử dụng công cụ xác thực JSON: Khi tôi gỡ lỗi các cấu trúc JSON phức tạp hoặc cố gắng đảm bảo dữ liệu của mình tuân thủ một định dạng cụ thể, tôi thường sử dụng một công cụ như JSON Formatter & Validator của ToolCraft. Đây là một cách nhanh chóng, dựa trên trình duyệt để kiểm tra lỗi cú pháp hoặc chỉ để có cái nhìn rõ ràng về JSON của bạn trước khi bạn gửi nó đến cơ sở dữ liệu. Rất tiện lợi để phát hiện những lỗi đơn giản có thể dẫn đến lỗi xác thực, đặc biệt với các quy tắc
$jsonSchemaphức tạp.
Các bước xác minh
Sau khi áp dụng một bản sửa lỗi (bằng cách sửa dữ liệu của bạn hoặc sửa đổi các quy tắc schema):
-
Thử lại thao tác: Cố gắng chèn hoặc cập nhật lại dữ liệu. Nếu lỗi không còn xuất hiện, đó là một dấu hiệu tốt.
-
Truy vấn collection: Sử dụng
db.yourCollectionName.find({ _id: yourDocumentId })hoặc một truy vấn liên quan để đảm bảo tài liệu đã được chèn hoặc cập nhật thành công và xuất hiện như mong đợi trong cơ sở dữ liệu. -
Kiểm tra các trường hợp biên: Nếu bạn đã sửa đổi schema, hãy thử chèn dữ liệu mà bây giờ sẽ vượt qua (nhưng trước đây đã thất bại) và dữ liệu mà vẫn sẽ thất bại để đảm bảo các quy tắc xác thực mới của bạn đang hoạt động như dự định.

