ทำไมพังพอนถึงมีทั้งแบบจำลองและแบบจำลอง?


96

วัตถุทั้งสองประเภทดูเหมือนจะอยู่ใกล้กันมากจนทั้งสองรู้สึกซ้ำซ้อน อะไรคือจุดสำคัญของการมีทั้ง schemas และ model?

คำตอบ:


61

แก้ไข:แม้ว่าสิ่งนี้จะมีประโยชน์สำหรับหลาย ๆ คน แต่ดังที่กล่าวไว้ในความคิดเห็นก็ตอบว่า "อย่างไร" มากกว่าเหตุผล โชคดีที่ว่าทำไมของคำถามที่ได้รับการตอบรับที่อื่น ๆ นอกจากนี้ยังมีคำตอบนี้ไปคำถามอื่น สิ่งนี้ได้รับการเชื่อมโยงในความคิดเห็นมาระยะหนึ่งแล้ว แต่ฉันตระหนักดีว่าหลายคนอาจไม่เข้าใจถึงสิ่งนั้นเมื่ออ่าน

บ่อยครั้งวิธีที่ง่ายที่สุดในการตอบคำถามประเภทนี้คือการยกตัวอย่าง กรณีนี้มีคนทำมาให้แล้ว :)

ดูที่นี่:

http://rawberg.com/blog/nodejs/mongoose-orm-nested-models/

แก้ไข:โพสต์ต้นฉบับ (ตามที่ระบุไว้ในความคิดเห็น) ดูเหมือนจะไม่มีอยู่อีกต่อไปดังนั้นฉันจึงทำซ้ำด้านล่าง มันควรจะกลับมาหรือว่าเพิ่งย้ายไปโปรดแจ้งให้เราทราบ

มันให้คำอธิบายที่ดีเกี่ยวกับการใช้ schema ภายในแบบจำลองในพังพอนและเหตุผลที่คุณต้องการทำและยังแสดงวิธีผลักดันงานผ่านโมเดลในขณะที่สคีมาเป็นข้อมูลเกี่ยวกับโครงสร้างเป็นต้น

โพสต์ต้นฉบับ:

เริ่มจากตัวอย่างง่ายๆของการฝังสคีมาในโมเดล

var TaskSchema = new Schema({
    name: String,
    priority: Number
});
 
TaskSchema.virtual('nameandpriority')
    .get( function () {
        return this.name + '(' + this.priority + ')';
    });
 
TaskSchema.method('isHighPriority', function() {
    if(this.priority === 1) {
        return true;
    } else {
        return false;
    }
}); 
 
var ListSchema = new Schema({
    name: String,
    tasks: [TaskSchema]
});
 
mongoose.model('List', ListSchema);
 
var List = mongoose.model('List');
 
var sampleList = new List({name:'Sample List'});

ฉันสร้างTaskSchemaวัตถุใหม่พร้อมข้อมูลพื้นฐานที่งานอาจมี แอททริบิวต์เสมือนของพังพอนถูกตั้งค่าเพื่อรวมชื่อและลำดับความสำคัญของงานอย่างสะดวก ฉันระบุเฉพาะ getter ที่นี่ แต่รองรับตัวตั้งค่าเสมือนด้วย

ฉันยังกำหนดวิธีการงานง่ายๆที่เรียกว่าisHighPriorityเพื่อสาธิตวิธีการทำงานกับการตั้งค่านี้

ในListSchemaคำจำกัดความคุณจะสังเกตเห็นว่าคีย์งานถูกกำหนดค่าให้เก็บอาร์เรย์ของTaskSchemaวัตถุอย่างไร คีย์งานจะกลายเป็นอินสแตนซ์DocumentArrayที่มีวิธีพิเศษในการจัดการกับเอกสาร Mongo ที่ฝังไว้

สำหรับตอนนี้ฉันส่งผ่านListSchemaวัตถุไปยังพังพอนเท่านั้นโมเดลและปล่อย TaskSchema ออก ในทางเทคนิคแล้วไม่จำเป็นต้องเปลี่ยนTaskSchemaเป็นโมเดลที่เป็นทางการเนื่องจากเราจะไม่บันทึกไว้ในคอลเล็กชันของตัวเอง ต่อไปฉันจะแสดงให้คุณเห็นว่ามันไม่เป็นอันตรายอะไรถ้าคุณทำและมันสามารถช่วยในการจัดระเบียบโมเดลทั้งหมดของคุณในลักษณะเดียวกันโดยเฉพาะอย่างยิ่งเมื่อพวกเขาเริ่มขยายไฟล์หลายไฟล์

ด้วยการListตั้งค่าโมเดลให้เพิ่มงานสองสามอย่างเข้าไปและบันทึกลงใน Mongo

var List = mongoose.model('List');
var sampleList = new List({name:'Sample List'});
 
sampleList.tasks.push(
    {name:'task one', priority:1}, 
    {name:'task two', priority:5}
);
 
sampleList.save(function(err) {
    if (err) {
        console.log('error adding new list');
        console.log(err);
    } else {
        console.log('new list successfully saved'); 
    }
});

แอ็ตทริบิวต์งานในอินสแตนซ์ของListโมเดลของเรา( simpleList) ทำงานเหมือนกับอาร์เรย์ JavaScript ทั่วไปและเราสามารถเพิ่มงานใหม่โดยใช้ push สิ่งสำคัญที่ต้องสังเกตคืองานจะถูกเพิ่มเป็นวัตถุ JavaScript ปกติ เป็นความแตกต่างเล็กน้อยที่อาจไม่สามารถเข้าใจได้ในทันที

คุณสามารถตรวจสอบได้จากเปลือก Mongo ว่ารายการและงานใหม่ถูกบันทึกลงใน mongo

db.lists.find()
{ "tasks" : [
    {
        "_id" : ObjectId("4dd1cbeed77909f507000002"),
        "priority" : 1,
        "name" : "task one"
    },
    {
        "_id" : ObjectId("4dd1cbeed77909f507000003"),
        "priority" : 5,
        "name" : "task two"
    }
], "_id" : ObjectId("4dd1cbeed77909f507000001"), "name" : "Sample List" }

ตอนนี้เราสามารถใช้ObjectIdเพื่อดึงSample Listและทำซ้ำผ่านงานต่างๆ

List.findById('4dd1cbeed77909f507000001', function(err, list) {
    console.log(list.name + ' retrieved');
    list.tasks.forEach(function(task, index, array) {
        console.log(task.name);
        console.log(task.nameandpriority);
        console.log(task.isHighPriority());
    });
});

isHighPriorityถ้าคุณเรียกว่าบิตสุดท้ายของรหัสคุณจะได้รับข้อผิดพลาดว่าเอกสารที่ฝังตัวไม่ได้มีวิธีการ ในเวอร์ชันปัจจุบันของ Mongoose คุณไม่สามารถเข้าถึงเมธอดบนสคีมาแบบฝังได้โดยตรง มีตั๋วเปิดสำหรับแก้ไขและหลังจากตั้งคำถามไปยัง Mongoose Google Group manimal45 ได้โพสต์วิธีแก้ปัญหาที่เป็นประโยชน์เพื่อใช้ในตอนนี้

List.findById('4dd1cbeed77909f507000001', function(err, list) {
    console.log(list.name + ' retrieved');
    list.tasks.forEach(function(task, index, array) {
        console.log(task.name);
        console.log(task.nameandpriority);
        console.log(task._schema.methods.isHighPriority.apply(task));
    });
});

หากคุณเรียกใช้รหัสนั้นคุณจะเห็นผลลัพธ์ต่อไปนี้ในบรรทัดคำสั่ง

Sample List retrieved
task one
task one (1)
true
task two
task two (5)
false

ด้วยวิธีแก้ปัญหาดังกล่าวเรามาเปลี่ยนTaskSchemaเป็นนางแบบพังพอน

mongoose.model('Task', TaskSchema);
 
var Task = mongoose.model('Task');
 
var ListSchema = new Schema({
    name: String,
    tasks: [Task.schema]
});
 
mongoose.model('List', ListSchema);
 
var List = mongoose.model('List');

TaskSchemaความหมายเป็นเช่นเดียวกับก่อนเพื่อให้ฉันทิ้งมันออกมา เมื่อเปลี่ยนเป็นโมเดลแล้วเรายังสามารถเข้าถึงวัตถุ Schema ที่อยู่ภายใต้การใช้สัญกรณ์จุด

มาสร้างรายการใหม่และฝังอินสแตนซ์โมเดลงานสองอินสแตนซ์ไว้ภายใน

var demoList = new List({name:'Demo List'});
 
var taskThree = new Task({name:'task three', priority:10});
var taskFour = new Task({name:'task four', priority:11});
 
demoList.tasks.push(taskThree.toObject(), taskFour.toObject());
 
demoList.save(function(err) {
    if (err) {
        console.log('error adding new list');
        console.log(err);
    } else {
        console.log('new list successfully saved'); 
    }
});

ในขณะที่เรากำลังฝังอินสแตนซ์แบบจำลองงานลงในรายการเรากำลังเรียกร้องให้toObjectพวกเขาแปลงข้อมูลเป็นวัตถุ JavaScript ธรรมดาที่List.tasks DocumentArrayคาดหวัง ObjectIdsเมื่อคุณบันทึกกรณีรูปแบบวิธีนี้เอกสารที่ฝังตัวของคุณจะมี

ตัวอย่างรหัสที่สมบูรณ์พร้อมใช้งานเป็นส่วนสำคัญ หวังว่าการแก้ไขปัญหาเหล่านี้จะช่วยให้ทุกอย่างราบรื่นในขณะที่พังพอนยังคงพัฒนาต่อไป ฉันยังใหม่กับ Mongoose และ MongoDB ดังนั้นโปรดอย่าลังเลที่จะแบ่งปันวิธีแก้ปัญหาและเคล็ดลับที่ดีกว่าในความคิดเห็น ขอให้มีความสุขกับการสร้างแบบจำลองข้อมูล!


3
โดยทั่วไปไม่แนะนำให้ส่งลิงก์เปล่าเป็นคำตอบสำหรับคำถามที่โพสต์ใน SO เนื่องจากลิงก์อาจหยุดทำงาน (เช่นในกรณีนี้) อย่างน้อยคัดลอก / ผ่านมาและอ้างถึงส่วนที่เกี่ยวข้องของบทความที่คุณลิงก์ไป
βξhrαng

1
เสร็จแล้ว - มันยังอยู่ในแคชของ Google ค่อนข้างง่าย
Adam Comerford

1
สำหรับบันทึกปัญหาวิธีการเอกสารแบบฝังได้รับการแก้ไขแล้ว: github.com/LearnBoost/mongoose/issues/249#ref-commit-e18077a
Dakota

6
ฉันไม่ได้พยายามให้ฝนตกในขบวนพาเหรดของใคร แต่คำตอบนี้อ่านเพิ่มเติมเหมือนบทช่วยสอน: ตอบว่าอย่างไร แต่ไม่ใช่เหตุผล แม้จะมีคะแนนโหวตน้อยกว่า แต่ฉันพบว่าคำตอบต่อไปนี้มีประโยชน์มากกว่ามาก: stackoverflow.com/a/22950402/26331
aaaidan

2
ฉันเคยเห็นคำตอบนั้น (และโหวตให้คะแนน) คำตอบนี้ได้รับคำตอบและยอมรับเมื่อ 2 ปีก่อนหน้านั้น ฉันดีใจที่มีคำตอบที่ดีกว่านี้ไม่มีฝนตกในขบวนพาเหรดของใครและมีลิงค์ไปยังคำตอบที่คุณอ้างถึงในความคิดเห็นของคำถามตั้งแต่เดือนกุมภาพันธ์ 2015 ดังนั้นฉันจึงไม่จำเป็นต้องเชื่อมโยงด้วยตัวเอง
Adam Comerford

55

Schemaเป็นวัตถุที่กำหนดโครงสร้างของเอกสารใด ๆ ที่จะถูกเก็บไว้ในคอลเล็กชัน MongoDB ของคุณ ช่วยให้คุณสามารถกำหนดประเภทและตัวตรวจสอบความถูกต้องสำหรับรายการข้อมูลทั้งหมดของคุณ

Modelเป็นวัตถุที่ช่วยให้คุณเข้าถึงคอลเล็กชันที่มีชื่อได้อย่างง่ายดายช่วยให้คุณสามารถสอบถามคอลเล็กชันและใช้ Schema เพื่อตรวจสอบความถูกต้องของเอกสารใด ๆ ที่คุณบันทึกลงในคอลเล็กชันนั้น สร้างขึ้นโดยการรวม Schema การเชื่อมต่อและชื่อคอลเล็กชัน

เดิมวลี Valeri Karpov, MongoDB Blog


1
คำตอบที่ดีที่สุดคือ 2-3 คำตอบด้านล่างคำตอบที่ยอมรับ: P
luG_0

5

ฉันไม่คิดว่าคำตอบที่ได้รับการยอมรับจะตอบคำถามที่ถูกโพสต์ได้จริง คำตอบไม่ได้อธิบายว่าทำไม Mongoose จึงตัดสินใจกำหนดให้นักพัฒนาจัดหาทั้ง Schema และตัวแปร Model ตัวอย่างของกรอบการทำงานที่ไม่จำเป็นต้องมีนักพัฒนาในการกำหนดสคีมาข้อมูลคือ django - นักพัฒนาจะเขียนโมเดลของตนในไฟล์ models.py และปล่อยให้เป็นเฟรมเวิร์กเพื่อจัดการสคีมา เหตุผลแรกที่คิดว่าทำไมพวกเขาถึงทำเช่นนี้จากประสบการณ์ของฉันกับ django คือใช้งานง่าย บางทีที่สำคัญกว่านั้นคือหลักการ DRY (อย่าทำซ้ำตัวเอง) - คุณไม่จำเป็นต้องจำไว้ว่าต้องอัปเดตสคีมาเมื่อคุณเปลี่ยนโมเดล - django จะทำเพื่อคุณ! Rails ยังจัดการสคีมาของข้อมูลให้คุณด้วย - นักพัฒนาไม่ได้แก้ไขสคีมาโดยตรง แต่เปลี่ยนแปลงโดยกำหนดการย้ายข้อมูลที่จัดการกับสคีมา

เหตุผลหนึ่งที่ฉันเข้าใจว่า Mongoose จะแยกสคีมาและโมเดลคืออินสแตนซ์ที่คุณต้องการสร้างโมเดลจากสคีมาสองแบบ สถานการณ์ดังกล่าวอาจทำให้เกิดความซับซ้อนมากกว่าที่ควรค่าแก่การจัดการหากคุณมีสคีมาสองแบบที่จัดการโดยโมเดลเดียวเหตุใดจึงไม่มีสคีมาเดียว

บางทีคำถามเดิมอาจเป็นที่ระลึกของระบบฐานข้อมูลเชิงสัมพันธ์แบบดั้งเดิม ในโลก NoSQL / Mongo บางทีสคีมาอาจมีความยืดหยุ่นมากกว่า MySQL / PostgreSQL เล็กน้อยดังนั้นการเปลี่ยนสคีมาจึงเป็นแนวทางปฏิบัติทั่วไป


ราวกับว่าสคีมากับโมเดลซ้ำตัวเองไม่เพียงพอคุณจะพบกับการทำซ้ำมากขึ้นเมื่อพยายามรักษาอินเทอร์เฟซ TypeScript ที่ตรงกันและมากยิ่งขึ้นเมื่อสร้างสคีมา GraphQL
Dan Dascalescu

1

จะเข้าใจว่าทำไม? คุณต้องเข้าใจว่าพังพอนคืออะไร?

พังพอนเป็นไลบรารีการสร้างแบบจำลองข้อมูลวัตถุสำหรับ MongoDB และ Node JS ซึ่งให้ระดับนามธรรมที่สูงขึ้น ดังนั้นมันจึงเหมือนกับความสัมพันธ์ระหว่าง Express และ Node เล็กน้อยดังนั้น Express จึงเป็นชั้นของนามธรรมเหนือ Node ทั่วไปในขณะที่ Mongoose เป็นชั้นของนามธรรมเหนือไดรเวอร์ MongoDB ทั่วไป

ไลบรารีการสร้างแบบจำลองข้อมูลออบเจ็กต์เป็นเพียงวิธีการเขียนโค้ด Javascript ที่จะโต้ตอบกับฐานข้อมูล ดังนั้นเราสามารถใช้ไดรเวอร์ MongoDB ปกติเพื่อเข้าถึงฐานข้อมูลของเราได้มันก็จะทำงานได้ดี

แต่เราใช้ Mongoose แทนเพราะทำให้เรามีฟังก์ชันการทำงานที่หลากหลายมากขึ้นทำให้เราสามารถพัฒนาแอปพลิเคชันของเราได้เร็วและง่ายขึ้น

ดังนั้นคุณสมบัติบางอย่างของ Mongoose จึงทำให้เรามีสคีมาในการสร้างแบบจำลองข้อมูลและความสัมพันธ์ของเราการตรวจสอบข้อมูลที่ง่าย API การสืบค้นอย่างง่ายมิดเดิลแวร์และอื่น ๆ อีกมากมาย

ในพังพอนสคีมาคือที่ที่เราสร้างแบบจำลองข้อมูลของเราโดยที่เราอธิบายโครงสร้างของข้อมูลค่าเริ่มต้นและการตรวจสอบความถูกต้องจากนั้นเราจะนำสคีมานั้นมาสร้างแบบจำลองโดยพื้นฐานแล้วโมเดลจะเป็นกระดาษห่อหุ้มรอบสคีมา ซึ่งช่วยให้เราสามารถเชื่อมต่อกับฐานข้อมูลเพื่อสร้างลบอัปเดตและอ่านเอกสาร

ใส่คำอธิบายภาพที่นี่

มาสร้างโมเดลจากสคีมากัน

const tourSchema = new mongoose.Schema({
  name: {
    type: String,
    required: [true, 'A tour must have a name'],
    unique: true,
  },
  rating: {
    type: Number,
    default: 4.5,
  },
  price: {
    type: Number,
    required: [true, 'A tour must have a price'],
  },
});
//tour model
const Tour = mongoose.model('Tour', tourSchema);

ตามตัวอักษรตัวแรกของชื่อรุ่นต้องเป็นตัวพิมพ์ใหญ่

มาสร้างอินสแตนซ์ของโมเดลของเราที่เราสร้างขึ้นโดยใช้พังพอนและสคีมา ยังโต้ตอบกับฐานข้อมูลของเรา

const testTour = new Tour({ // instance of our model
  name: 'The Forest Hiker',
  rating: 4.7,
  price: 497,
});
 // saving testTour document into database
testTour
  .save()
  .then((doc) => {
    console.log(doc);
  })
  .catch((err) => {
    console.log(err);
  });

ดังนั้นการมีพังพอนทั้ง schama และ modle ทำให้ชีวิตของเราง่ายขึ้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.