การเผยแพร่และบอกรับข้อมูล

บทแทรก 4.5

แปลไปแล้ว

ในบทนี้ คุณจะได้

  • เข้าใจการทำงานของการเผยแพร่และบอกรับข้อมูล
  • เรียนรู้ว่าแพ็คเกจ Autopublish ทำงานอย่างไร
  • ตัวอย่างแบบต่างๆของการเผยแพร่ข้อมูล
  • การเผยแพร่และบอกรับข้อมูล เป็นหนึ่งในพื้นฐานและแนวคิดหลักของ Meteor ซึ่งอาจจะทำให้คุณหัวหมุนได้เมื่อเริ่มต้นใช้มัน

    มีความเข้าใจผิดๆเกี่ยวกับเรื่องนี้อยู่มาก เช่น เชื่อว่า Meteor ไม่มีความปลอดภัย หรือแอพ Meteor ไม่สามารถทำงานกับข้อมูลปริมาณมากๆได้

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

    วันเก่าๆ

    ก่อนอื่นเราลองมองย้อนกลับไปในอดีตตอนปี 2011 ที่ยังไม่มี Meteor กันดู สมมุติว่าตอนนั้นคุณกำลังสร้างเว็บแอพด้วย Rails แบบง่ายๆ เมื่อมีคนเปิดเข้ามาที่เว็บไซต์คุณ ไคลเอนต์ (เช่น เบราว์เซอร์) จะส่งคำร้องขอข้อมูลเข้ามาที่แอพคุณซึ่งรันอยู่บนเซิร์ฟเวอร์

    สิ่งแรกที่แอพทำคือ หาว่าผู้ใช้ต้องการเห็นข้อมูลอะไร อาจจะเป็นหน้า 12 ของผลการค้นหา, ประวัติย่อของแมรี่, 20 ทวิตล่าสุดของบ๊อบ หรืออื่นๆ คุณอาจเปรียบแอพเป็นเหมือนพนักงานในร้านหนังสือกำลังค้นที่ชั้นหนังสือหาหนังสือเรื่องที่คุณต้องการอยู่ก็ได้

    เมื่อได้ข้อมูลที่ต้องการแล้ว สิ่งต่อมาที่แอพทำคือ แปลงข้อมูลนั้นให้เป็น HTML ที่เราอ่านได้ (หรือเป็น JSON ถ้าแอพเป็น API)

    มองในมุมของร้านหนังสือ ก็เปรียบได้กับการหุ้มปกหนังสือที่คุณเพิ่งซื้อและใส่ถุงสวยๆ ซึ่งตรงนี้ก็คือ “View” ในโมเดล MVC ที่คนพูดถึงนั่นเอง

    สุดท้าย แอพก็นำ HTML นั้นส่งกลับมาให้เบราว์เซอร์ และจบการทำงาน จากนั้นมันก็ว่างและอาจเพลินอยู่กับเบียร์ในขณะที่รอคำร้องขอข้อมูลถัดไปอยู่ก็ได้

    วิธีของ Meteor

    ตอนนี้ลองมาดูสิ่งที่ทำให้ Meteor มีความพิเศษต่างออกไป เราได้เห็นนวัตกรรมหลักที่ Meteor นำมาใช้คือ ในขณะที่แอพ Rails ทำงานอยู่ บนเซิร์ฟเวอร์ แต่แอพ Meteor ส่วนหนึ่งจะรัน ที่ไคลเอนต์ (ในเบราว์เซอร์) ด้วย

    Pushing a subset of the database to the client.
    Pushing a subset of the database to the client.

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

    สถาปัตยกรรมนี้ช่วยให้ Meteor ทำอะไรที่น่าสนใจได้หลายอย่าง ตัวเด่นๆก็คือที่ Meteor เรียกว่า ฐานข้อมูลอยู่ทั่วทุกที่ (database everywhere) พูดง่ายๆคือ Meteor จะนำชุดข้อมูลย่อยของฐานข้อมูลมา ทำสำเนา และส่งให้ ไคลเอนต์ ทุกตัว

    วิธีการนี้ทำให้เกิดเรื่องที่เกี่ยวข้องสองเรื่องใหญ่ๆ คือ เรื่องแรก แทนที่จะส่งโค้ด HTML ไปให้ไคลเอนต์ Meteor จะส่ง ข้อมูลจริง หรือข้อมูลดิบ ไปให้ไคลเอนต์จัดการเอง (data on the wire) เรื่องที่สอง คุณสามารถที่จะ เข้าถึงและแม้กระทั่งแก้ไขข้อมูลนั้นได้ทันที โดยไม่ต้องรอให้ข้อมูลวนกลับไปที่เซิร์ฟเวอร์อีกรอบ (latency compensation)

    การเผยแพร่ข้อมูล

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

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

    ตอนนี้ถ้าเรากลับไปดูที่แอพ Microscope จะเห็นว่ารายการข่าวที่โพสต์ถูกเก็บไว้ในฐานข้อมูลแบบนี้

    All the posts contained in our database.
    All the posts contained in our database.

    ถึงแม้ในตอนนี้ Microscope จะยังไม่มีคุณสมบัติที่จะระบุลงไปว่าข่าวตัวไหนใช้ภาษาที่ไม่เหมาะสม และเรายังเก็บข่าวพวกนี้ไว้ในฐานข้อมูล เราก็ไม่ควรให้ผู้ใช้เห็น (ก็คือไม่ต้องส่งไปให้ไคลเอนต์)

    ในกรณีนี้ สิ่งแรกที่เราจะทำคือ บอก Meteor ว่าข่าวไหนที่เรา ต้องการ ส่งไปให้ไคลเอนต์ โดยเราจะบอก Meteor ให้ เผยแพร่ เฉพาะข่าวที่ผู้ใช้ควรเห็นเท่านั้น

    Excluding flagged posts.
    Excluding flagged posts.

    ข้างล่างนี้คือ โค้ดที่เราต้องการ ซึ่งจะรันอยู่บนฝั่งเซิร์ฟเวอร์

    // on the server
    Meteor.publish('posts', function() {
      return Posts.find({flagged: false});
    });
    

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

    DDP

    โดยพื้นฐานแล้ว คุณสามารถมองระบบเผยแพร่และรับข้อมูลนี้ เหมือนกับท่อที่ใช้ส่งข้อมูลจากคอลเลคชั่นบนเซิร์ฟเวอร์ (ต้นทาง) ไปที่คอลเลคชั่นที่ไคลเอนต์ (ปลายทาง)

    โดยโปรโตคอลที่ใช้ในท่อนี้เรียกว่า DDP (ย่อมาจาก Distributed Data Protocol) ถ้าคุณต้องการเรียนรู้เพิ่มเติมในเรื่องนี้ คุณสามารถเข้าไปดู การบรรยายจากงานประชุมเรื่องเรียลไทม์ โดย Matt DeBergalis (หนึ่งในผู้ก่อตั้ง Meteor) หรือที่ สกรีนคาสต์นี้ โดย Matt DeBergalis ซึ่งจะช่วยให้คุณเข้าใจแนวคิดของเรื่องนี้ในรายละเอียดที่มากขึ้นอีกนิด

    การบอกรับข้อมูล

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

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

    ตัวอย่างที่เห็น จะสมมุติว่าเรากำลังดูหน้าประวัติย่อของ Bob Smith อยู่ และต้องการจะดูเฉพาะข่าว ของเค้า เท่านั้น

    Subscribing to Bob's posts will mirror them on the client.
    Subscribing to Bob’s posts will mirror them on the client.

    แรกสุด เราต้องปรับแก้ฟังก์ชันการเผยแพร่ข้อมูล ให้รับพารามิเตอร์หนึ่งตัว

    // on the server
    Meteor.publish('posts', function(author) {
      return Posts.find({flagged: false, author: author});
    });
    

    และเราก็ระบุค่าให้พารามิเตอร์นั้นตอนที่เรา บอกรับ ข้อมูลจากการเผยแพร่นั้น ในโค้ดฝั่งไคลเอนต์ของแอพเรา

    // on the client
    Meteor.subscribe('posts', 'bob-smith');
    

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

    การค้นหา

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

    Selecting a subset of documents on the client.
    Selecting a subset of documents on the client.

    ก็เหมือนกับที่เราทำบนโค้ดฝั่งเซิร์ฟเวอร์ เราจะใช้คำสั่ง Posts.find() มากรองเอาเฉพาะข้อมูลย่อยของเราเท่านั้น

    // on the client
    Template.posts.helpers({
      posts: function(){
        return Posts.find(author: 'bob-smith', category: 'JavaScript');
      }
    });
    

    ถึงตรงนี้เราก็มีความเข้าใจในบทบาทของการเผยแพร่และการบอกรับข้อมูลกันเป็นอย่างดีแล้ว ก็ได้เวลาที่เราจะมาเจาะลึกและสำรวจดูรูปแบบการทำงานสองสามอย่างที่ใช้กันทั่วไป

    การเผยแพร่อัตโนมัติ (Autopublish)

    ถ้าคุณสร้างแอพ Meteor ตั้งแต่เริ่มต้น (โดยใช้ meteor create) มันก็จะเปิดให้แพ็คเกจ autopublish ทำงานโดยอัตโนมัติ เพื่อให้เราเข้าใจตั้งแต่ต้น เราก็จะมาดูว่ามันทำงานอย่างไรกันแน่

    เป้าหมายของ autopublish คือ ทำให้คุณเขียนโค้ดเพื่อสร้างแอพ Meteor ได้ง่ายๆ แะมันทำสิ่งนี้ด้วยการสำเนา ข้อมูลทั้งหมด อย่างอัตโนมัติจากเซิร์ฟเวอร์มาที่ไคลเอนต์ โดยจัดการเรื่องการเผยแพร่และบอกรับข้อมูลให้คุณทั้งหมด

    Autopublish
    Autopublish

    แล้วมันทำงานอย่างไร คำอธิบายก็คือ สมมุติว่าคุณมีคอลเลคชั่นชื่อ 'posts' บนเซิร์ฟเวอร์ autopublish จะส่งข้อมูลทุกโพสต์ที่พบในคอลเลคชั่นโพสต์ของ Mongo ไปที่คอลเลคชั่นชื่อ 'posts' ที่ไคลเอนต์ (สมมุติว่ามีอย่างน้อยหนึ่งตัว)

    ดังนั้นถ้าคุณกำลังใช้ autopublish อยู่ คุณก็ไม่จำเป็นต้องคิดถึงเรื่องการเผยแพร่ข้อมูล เพราะข้อมูลเหมือนกันทุกที่ และทุกอย่างก็ง่ายไปหมด แต่ก็แน่นอนว่าจะมีปัญหาเรื่องที่ข้อมูลของแอพคุณไปเก็บที่เครื่องของผู้ใช้ทุกเครื่อง

    ด้วยเหตุนี้ การใช้วิธีเผยแพร่อัตโนมัติ จึงเหมาะที่จะใช้ในตอนเริ่มต้น และยังไม่ได้คิดเรื่องการเผยแพร่ข้อมูลเลย

    การเผยแพร่ทั้งคอลเลคชั่น

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

    Meteor.publish('allPosts', function(){
      return Posts.find();
    });
    
    Publishing a full collection
    Publishing a full collection

    จะเห็นว่าเรายังคงเผยแพร่ข้อมูลทั้งคอลเลคชั่น แต่อย่างน้อยเราก็ควบคุมว่าคอลเลคชั่นไหนที่เราจะเผยแพร่ หรืออันไหนที่เราไม่ทำ ในตัวอย่างนี้เราทำการเผยแพร่แค่คอลเลคชั่น Posts แต่ยกเว้น Comments

    การเผยแพร่บางส่วนของคอลเลคชั่น

    ขั้นต่อไปที่เราจะควบคุมก็คือ การเผยแพร่ บางส่วน ของคอลเลคชั่น ในตัวอย่าง เราต้องการแค่โพสต์ที่เป็นของผู้แต่งบางคนเท่านั้น

    Meteor.publish('somePosts', function(){
      return Posts.find({'author':'Tom'});
    });
    
    Publishing a partial collection
    Publishing a partial collection

    เบื้องหลังการทำงาน

    ถ้าคุณได้อ่าน เอกสารเรื่องการเผยแพร่ข้อมูลของ Meteor คุณน่าจะเห็นการอธิบายวิธีใช้ add() และ ready() เพื่อตั้งค่าแอททริบิวท์ของรายการข้อมูลที่ฝั่งไคลเอนต์มาพอสมควร และรู้สึกขัดแย้งว่า แอพ Meteor ที่คุณเคยเห็นมานั้นไม่เคยใช้คำสั่งพวกนี้เลย

    เหตุผลก็คือ Meteor มีวิธีอื่นที่สะดวกสบายกว่านั้นมาก ด้วยคำสั่ง _publishCursor() ที่คุณก็ไม่เคยเห็นใครใช้มันเหมือนกัน แต่ถ้าคุณคืนค่า เคอร์เซอร์ (ด้วยคำสั่ง Posts.find({'author':'Tom'})) ในฟังก์ชัน publish เมื่อไหร่ Meteor ก็จะเรียกใช้มันทันที

    เมื่อ Meteor เห็นการเผยแพร่ข้อมูล somePosts ที่ส่งคืนค่าเคอร์เซอร์ มันจะเรียกใช้ _publishCursor() เพื่อ (คุณลองเดาดู) เผยแพร่เคอร์เซอร์นั้นโดยอัตโนมัติ

    โดยฟังก์ชัน _publishCursor() ทำหน้าที่ต่อไปนี้

    • ตรวจสอบชื่อของคอลเลคชั่นบนเซิร์ฟเวอร์
    • ดึงเอกสารที่ตรงตามเงื่อนไขจากเคอร์เซอร์ และส่งไปที่คอลเลคชั่นของไคลเอนต์ที่มี ชื่อเหมือนกัน (โดยเรียกใช้ .added() ให้ทำงานนี้)
    • เมื่อใดที่เอกสารถูกเพิ่ม ลบออก หรือมีการแก้ไข มันจะส่งค่าการเปลี่ยนแปลงนั้นไปที่คอลเลคชั่นของไคลเอนต์ (โดยใช้ .observe() กับเคอร์เซอร์ และ .added(), .changed(), removed() เพื่อทำงานนี้)

    ดังนั้นจากตัวอย่างข้างบน เราก็แน่ใจได้ว่าผู้ใช้จะได้รับแค่โพสต์ที่สนใจ (เขียนโดย Tom) มาเก็บอยู่ในแคชที่ไคลเอนต์พร้อมสำหรับการเรียกใช้งานทันที

    การเผยแพร่บางฟิลด์ของข้อมูล

    เราได้เห็นวิธีการเผยแพร่แค่บางส่วนของโพสต์แล้ว แต่เราก็ยังจะเฉือนข้อมูลให้บางลงอีก! ลองมาดูกันว่าจะทำอย่างไรเมื่อต้องการเผยแพร่แค่ บางฟิลด์ เท่านั้น

    ก็เหมือนกับก่อนหน้านี้ เราจะใช้ find() เพื่อส่งคืนค่าเคอร์เซอร์ แต่ตอนนี้เราจะตัดบางฟิลด์ออก

    Meteor.publish('allPosts', function(){
      return Posts.find({}, {fields: {
        date: false
      }});
    });
    
    Publishing partial properties
    Publishing partial properties

    และก็แน่นอนว่าเราสามารถผสมเทคนิคทั้งสองเข้าด้วยการ เช่น ถ้าเราต้องการส่งโพสต์ของทอม โดยตัดวันที่ออก เราก็จะเขียนโค้ดแบบนี้

    Meteor.publish('allPosts', function(){
      return Posts.find({'author':'Tom'}, {fields: {
        date: false
      }});
    });
    

    สรุป

    เราได้เห็นวิธีการเผยแพร่ข้อมูล ตั้งแต่ทุกฟิลด์ของทุกเอกสารของทุกคอลเลคชั่น (ด้วย autopublish) จนถึงการเผยแพร่แค่ บางฟิลด์ ของ บางเอกสาร ของ บางคอลเลคชั่น

    วิธีการเหล่านี้ครอบคลุมพื้นฐานที่คุณสามารถใช้กับการเผยแพร่ข้อมูลใน Meteor และเทคนิคง่ายๆ พวกนี้ก็ช่วยในการใช้งานได้หลากหลายกรณี

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