6 คะแนน โดย GN⁺ 2025-09-23 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Cap'n Web คือ โปรโตคอล RPC ใหม่ที่พัฒนาด้วย TypeScript โดยออกแบบมาให้เหมาะกับสภาพแวดล้อมเว็บและทำงานได้บน JavaScript runtime ที่หลากหลาย
  • โดยไม่ต้องมีสคีมาหรือ boilerplate ที่ยุ่งยาก ก็สามารถใช้ การซีเรียลไลซ์แบบอิง JSON และ รูปแบบข้อมูลที่มนุษย์อ่านได้
  • ด้วย โมเดลแบบ object-capability จึงรองรับ การเรียกแบบสองทาง, การส่งต่อ reference ของฟังก์ชันและอ็อบเจ็กต์, promise pipelining และการสร้างแพตเทิร์นด้านความปลอดภัย
  • รองรับสภาพแวดล้อมเครือข่ายหลากหลาย เช่น WebSocket, HTTP, postMessage และเป็นโอเพนซอร์สน้ำหนักเบาขนาดต่ำกว่า 10kB
  • ไม่เพียงช่วยแก้ปัญหา waterfall แบบเดียวกับ GraphQL เท่านั้น แต่ยังทำให้สามารถออกแบบ RPC ได้อย่างเป็นธรรมชาติคล้าย JavaScript API ทั่วไป

Cap'n Web คืออะไร

  • Cap'n Web คือ ระบบ RPC โอเพนซอร์สบนพื้นฐาน TypeScript ที่พัฒนาโดย Cloudflare
  • ได้แรงบันดาลใจจาก Cap'n Proto แต่ทำงานได้โดย ไม่ต้องกำหนดสคีมาแยกต่างหาก และใช้ JSON เป็นวิธี ซีเรียลไลซ์ที่เป็นมิตรต่อมนุษย์
  • ทำงาน ผสานกับ TypeScript ช่วยยกระดับประสบการณ์นักพัฒนา เช่น autocomplete และ type check ส่วนการตรวจสอบ type ตอนรันไทม์สามารถจัดการแยกต่างหากได้ เช่น type guard
  • รองรับ โปรโตคอลเครือข่ายอย่าง HTTP, WebSocket, postMessage และทำงานได้บนเบราว์เซอร์หลัก, Cloudflare Workers, Node.js เป็นต้น
  • มีโครงสร้างแบบเบาโดยไม่มี dependency และมีขนาด ต่ำกว่า 10kB เมื่อ minify + gzip

โมเดล object-capability (OCap) ของ Cap'n Web

  • ใช้ โมเดลแบบ object-capability ทำให้แสดงรูปแบบการทำงานได้หลากหลายกว่าระบบ RPC แบบเดิม
    • การเรียกแบบสองทาง: ไคลเอนต์และเซิร์ฟเวอร์สามารถเรียกฟังก์ชันของกันและกันได้
    • การส่งต่อ reference ของฟังก์ชันและอ็อบเจ็กต์: เมื่อส่งฟังก์ชันหรืออ็อบเจ็กต์ผ่าน RPC ฝั่งตรงข้ามจะได้รับสตับและเมื่อเรียกใช้งานจะไปทำงานที่ต้นทาง
    • Promise Pipelining: เมื่อเชื่อมหลาย RPC เป็น chain สามารถประมวลผลได้ด้วยการรับส่งข้อมูลไป-กลับบนเครือข่ายเพียงครั้งเดียว
    • แพตเทิร์นด้านความปลอดภัย: สามารถสร้างกลไกควบคุมด้านความปลอดภัย เช่น การมอบสิทธิ์และการจัดการเซสชัน ได้อย่างเป็นธรรมชาติ

วิธีใช้งานพื้นฐาน

  • ตัวอย่างไคลเอนต์

    import { newWebSocketRpcSession } from "capnweb"  
    let api = newWebSocketRpcSession("wss://example.com/api")  
    let result = await api.hello("World")  
    console.log(result)  
    
  • ตัวอย่างเซิร์ฟเวอร์ (อิง Cloudflare Worker)

    import { RpcTarget, newWorkersRpcResponse } from "capnweb"  
    class MyApiServer extends RpcTarget {  
      hello(name) {  
        return `Hello, ${name}!`  
      }  
    }  
    export default {  
      fetch(request, env, ctx) {  
        let url = new URL(request.url)  
        if (url.pathname === "/api") {  
          return newWorkersRpcResponse(request, new MyApiServer())  
        }  
        return new Response("Not found", {status: 404})  
      }  
    }  
    
  • สามารถเพิ่มเมธอดให้ API, ส่ง callback function จากไคลเอนต์, รวมถึงกำหนดและนำ TypeScript interface ไปใช้ได้อย่างง่ายดาย

RPC คืออะไร และจุดเด่นของ Cap'n Web

  • RPC (Remote Procedure Call) คือแนวคิดที่ทำให้โปรแกรมสองตัวบนเครือข่ายสื่อสารกันได้ราวกับเป็นการเรียกฟังก์ชัน
  • ต่างจากโปรโตคอล HTTP/REST แบบดั้งเดิม RPC ใช้ abstraction แบบการเรียกฟังก์ชัน ทำให้เขียนโค้ดได้ สอดคล้องกับวิธีคิดของนักพัฒนา
  • Cap'n Web เข้ากันได้ดีกับแนวทางของ JavaScript ยุคใหม่ เช่น async/await, Promise, Exception
  • จากเดิมที่ RPC เคยมีข้อถกเถียงทางประวัติศาสตร์ เช่น การเรียกแบบ synchronous และข้อผิดพลาดเครือข่าย ในสภาพแวดล้อม JS สมัยใหม่สามารถใช้งานได้อย่างปลอดภัยและมีประสิทธิภาพมากขึ้น

สถานการณ์การใช้งานของ Cap'n Web

  • เหมาะกับทุกสภาพแวดล้อมที่ต้องการ การสื่อสารผ่านเครือข่ายระหว่าง JavaScript application สองฝั่ง
    • เช่น การเรียกระหว่างไคลเอนต์-เซิร์ฟเวอร์ หรือระหว่างไมโครเซอร์วิส
    • โดยเฉพาะเว็บแอป ทำงานร่วมกันแบบเรียลไทม์ และการโต้ตอบที่ต้องข้ามขอบเขตความปลอดภัยซับซ้อน
  • ยังอยู่ในระยะทดลอง จึงเหมาะยิ่งกับนักพัฒนาที่เปิดรับเทคโนโลยีใหม่

ความสามารถหลากหลาย

โหมด HTTP batch

  • หากไม่จำเป็นต้องเชื่อมต่อแบบต่อเนื่อง สามารถใช้ โหมด HTTP batch เพื่อรวมหลาย RPC call แล้วประมวลผลพร้อมกันได้

    import { newHttpBatchRpcSession } from "capnweb"  
    let batch = newHttpBatchRpcSession("https://example.com/api";)  
    let result = await batch.hello("World")  
    console.log(result)  
    
  • สามารถรันหลายคำสั่งเรียกภายใน batch เดียวพร้อมกัน และรับผลลัพธ์แบบขนานได้

    let promise1 = batch.hello("Alice")  
    let promise2 = batch.hello("Bob")  
    let [result1, result2] = await Promise.all([promise1, promise2])  
    

Promise Pipelining (การเรียกแบบ chain)

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

  • ตัวอย่าง) ส่ง Promise ของผลลัพธ์จาก getMyName() เข้า hello() ได้ทันที และประมวลผลด้วยการรับส่งข้อมูลไป-กลับเพียงครั้งเดียว

    let namePromise = batch.getMyName()  
    let result = await batch.hello(namePromise)  
    
  • Promise ของ Cap'n Web ทำงานเป็น อ็อบเจ็กต์ proxy จึงสามารถ chain การเรียกเมธอดเพิ่มเติมได้โดยไม่เกิดความล่าช้า

    let sessionPromise = batch.authenticate(apiKey)  
    let name = await sessionPromise.whoami()  
    

ความปลอดภัย: การยืนยันตัวตนและ object-capability

  • ผ่านเมธอด authenticate สามารถ มอบอ็อบเจ็กต์สิทธิ์ใช้งาน (session) เมื่อสำเร็จ และหลังจากนั้นเรียกใช้ความสามารถต่าง ๆ ได้โดยไม่ต้องผ่านขั้นตอนยืนยันตัวตนเพิ่ม
  • ต่างจาก RPC แบบเดิม ไม่สามารถปลอมแปลง session object ได้ และไม่อาจเข้าถึงเมธอดที่ต้องใช้สิทธิ์โดยไม่มีการยืนยันตัวตน
  • ช่วยก้าวข้ามข้อจำกัดเชิงโครงสร้างของ WebSocket ได้อย่างเป็นธรรมชาติ และทำให้ตรรกะการยืนยันตัวตนมีความสอดคล้องกัน
  • เมื่อประกาศ API interface ด้วย TypeScript ก็สามารถนำไปใช้กับทั้งไคลเอนต์และเซิร์ฟเวอร์ได้โดยอัตโนมัติ พร้อมรองรับ autocomplete และ type safety

เปรียบเทียบกับ GraphQL และจุดแตกต่างของ Cap'n Web

  • GraphQL ช่วยบรรเทาปัญหา waterfall แบบ REST แต่ต้องนำ ภาษา, สคีมา, toolchain ใหม่ เข้ามาใช้

  • Cap'n Web แก้ปัญหา waterfall ได้ด้วยโค้ด JavaScript เพียงอย่างเดียว และ

    • ด้วยการรองรับ promise pipelining/object reference จึงสามารถโมเดล nested call หรือ logic ของธุรกรรมที่ซับซ้อนได้อย่างเป็นธรรมชาติ
    let user = api.createUser({ name: "Alice" })  
    let friendRequest = await user.sendFriendRequest("Bob")  
    
  • สามารถใช้งานได้คล้าย JavaScript API โดยไม่ต้องแบกรับ ความซับซ้อนและต้นทุนในการเรียนรู้หรือดูแลรักษา แบบ GraphQL

การทำงานกับอาร์เรย์ (array.map เป็นต้น) และการเพิ่มประสิทธิภาพ

  • ใน Cap'n Web สามารถทำ map กับแต่ละสมาชิกของอาร์เรย์ได้โดยไม่ต้องเพิ่มการรับส่งข้อมูลไป-กลับบนเครือข่าย

  • callback function ของ map จะถูกรันหนึ่งครั้งที่ฝั่งไคลเอนต์เพื่อบันทึกเนื้อหาการคำนวณแบบ record-replay จากนั้นส่งไปยังเซิร์ฟเวอร์เพื่อประมวลผลแบบรวมชุด

    let friendsWithPhotos = friendsPromise.map(friend => {  
      return {friend, photo: api.getUserPhoto(friend.id)}  
    })  
    let results = await friendsWithPhotos  
    
  • ผ่าน DSL เฉพาะโดเมนที่จำกัด ทำให้เขียนได้เหมือนฟังก์ชัน JavaScript แต่เบื้องหลังใช้โปรโตคอลของ Cap'n Web เพื่อเพิ่มประสิทธิภาพการเรียกหลายครั้ง

โครงสร้างโปรโตคอลภายในและลำดับการสื่อสาร

  • ส่งข้อมูลแบบมีโครงสร้างด้วย JSON + การประมวลผลล่วงหน้าแบบพิเศษ และรองรับชนิดข้อมูลพิเศษ เช่น อาร์เรย์และวันที่
  • เป็นโปรโตคอลแบบสมมาตร จึงรองรับ การสื่อสารสองทางโดยไม่แบ่งแยกไคลเอนต์/เซิร์ฟเวอร์
  • แต่ละฝั่ง (เช่น Alice และ Bob) จะจัดการตาราง export/import และแยก reference ของอ็อบเจ็กต์กับฟังก์ชันด้วย ID
  • ด้วยข้อความแบบ push/pull และการกำหนด Promise ID ทำให้สามารถสะท้อนหลายคำสั่งเรียกได้ภายในการรับส่งข้อมูลรอบเดียว

สถานะปัจจุบันและกรณีใช้งานจริง

  • ขณะนี้ Cap'n Web ยังเป็น โอเพนซอร์สเชิงทดลอง และถูกนำไปใช้ในบริการจริงแล้ว เช่น remote bindings ของ Cloudflare Wrangler
  • มีแผนจะเผยแพร่บล็อกโพสต์เพิ่มเติมและทำการทดลองกับฝั่งฟรอนต์เอนด์อีกหลากหลายรูปแบบ
  • เผยแพร่ภายใต้สัญญาอนุญาต MIT และ ทุกคนนำไปใช้ได้อย่างอิสระ
  • ไปยัง GitHub repository

1 ความคิดเห็น

 
GN⁺ 2025-09-23
ความคิดเห็นบน Hacker News
  • สงสัยอยู่สองเรื่อง

    1. อยากรู้ว่าควรจัดการการ deploy แอปที่มีการอัปเดต semantics ของ RPC อย่างไร กล่าวคือจะรับประกันได้อย่างไรว่าฝั่ง client และ server ใช้ RPC เวอร์ชันเดียวกัน โปรโตคอลบัฟเฟอร์ (grpc/avro เป็นต้น) พยายามแก้ปัญหานี้โดยตรง
    2. อยากรู้ว่าควรจัดการกับการเชื่อมต่อเครือข่ายที่ไม่เสถียรอย่างไร คิดว่า export/import table ถูกผูกตรงกับการเชื่อมต่อ WebSocket ที่มี state ดังนั้นถ้าการเชื่อมต่อหลุดก็น่าจะสูญเสีย state ไป แม้ในทางทฤษฎี client/server จะ cache state และกู้คืนตอน reconnect ได้ แต่ table อาจมี closure อยู่ด้วย ทำให้ serialize ยากและอาจมีปัญหาเรื่องหน่วยความจำ เลยอยากรู้ว่าทีมคิดเรื่องนี้ไว้อย่างไร
      คิดว่านี่เป็นงานที่ล้ำมาก
      1. มองให้คล้ายกับการอัปเดต JavaScript API โดยไม่ทำให้ caller เดิมพัง ตราบใดที่ทำตามหลักความเข้ากันได้แบบเดียวกับที่ใช้กับการเรียกฟังก์ชันภายในเครื่อง ก็สามารถเพิ่มเมธอดใหม่/อาร์กิวเมนต์แบบ optional ได้
      2. ถ้าการเชื่อมต่อหลุด ก็ต้องเชื่อมใหม่และสร้าง object ขึ้นมาใหม่ตั้งแต่ต้น ใน React app จริง เราจะส่ง main RPC stub เป็นอาร์กิวเมนต์ให้คอมโพเนนต์ระดับบนสุด จากนั้นคอมโพเนนต์นี้จะสร้าง object ย่อยหลายตัวแล้วส่งลงไปให้ลูก ๆ ถ้าการเชื่อมต่อหลุด ก็สร้าง stub ใหม่แล้วส่งให้คอมโพเนนต์บนสุดอีกครั้ง จากนั้นจะเกิดการ re-render เหมือน state change อื่น ๆ และลูกทุกตัวก็จะ fetch object ย่อยที่ต้องการใหม่
        ถ้ามี object แบบ subscription ที่มี callback อยู่ด้วย ก็ควรออกแบบ API ให้สามารถระบุ “ข้อความล่าสุดที่เห็น” ได้ตอนเริ่มต้น แบบนี้ก็จะรับข้อมูลต่อได้ทันทีโดยไม่ตกหล่นช่วงกลาง
        น่าจะต้องเขียนซีรีส์บล็อกโพสต์สรุป design pattern พวกนี้สักที
  • ส่วนที่อธิบายว่าแก้ปัญหา array อย่างไรน่าสนใจมาก และก็น่ากลัวนิด ๆ ไปพร้อมกัน ลิงก์บล็อก
    ในกรณีของ .map() แม้จะไม่ได้ส่ง JavaScript code ไปยัง server โดยตรง แต่ก็ส่งบางอย่างที่คล้าย “โค้ด” โดยใช้ภาษาเฉพาะโดเมน (DSL) แบบจำกัด ฝั่ง client จะลองรัน callback หนึ่งครั้งโดยใส่ placeholder value แล้วติดตามพฤติกรรมแบบ record-replay เพื่อส่ง instruction set ไปที่ server จากนั้น server จะรับ instruction เหล่านั้นและรันกับสมาชิกแต่ละตัวใน array
    กล่าวคือ นักพัฒนาแค่ใช้เมธอด js ตามปกติ แต่เบื้องหลังจะมีทริกแปลงสิ่งนี้ให้กลายเป็น DSL ที่แคบลง callback ต้องทำงานแบบ synchronous เท่านั้น และใช้ await ไม่ได้ อนุญาตเฉพาะ promise pipelining เพื่อให้จับทั้งกระบวนการได้หมดแล้วส่งต่อไปยัง server ซึ่งฝั่ง server จะ re-execute เมื่อจำเป็น

    • ใน C# มี expression tree สำหรับจัดการปัญหาแบบนี้ Entity Framework ใช้มันเวลาแปลง lambda expression ให้เป็น SQL query กล่าวคือสามารถสแกนหรือแปลงโค้ดได้โดยไม่ต้องรันจริง
      ตัวอย่างเช่น db.People.Where(p => p.Name == "Joe") นั้น Where ไม่ได้รับ predicate function จริง ๆ แต่รับ expression จึงสามารถสแกนโค้ดที่ส่งมา ดูว่า field Name ตรงกับ "Joe" หรือไม่ แล้วแปลงเป็น SQL WHERE clause ได้
      JavaScript ไม่มีกลไกแบบนี้ จึงต้องใช้วิธีใส่ placeholder value แล้วคอยบันทึกทีละอย่างว่ามันทำงานอย่างไรเพื่อเลียนแบบ

    • ตอนทำ query DSL ของ Tanstack DB เมื่อไม่นานมานี้ก็ใช้ทริก record-replay แบบเดียวกัน ลิงก์ไกด์ โดยส่ง object RefProxy เข้าไปใน callback ของ where/select/join แล้วติดตามว่ามี prop/operation อะไรเกิดขึ้นกับ object นั้นบ้าง
      ใน js เรา intercept operator ทั่วไป (==, > เป็นต้น) โดยตรงไม่ได้ เลยต้องสร้างฟังก์ชันเล็ก ๆ ที่ trace ได้อย่าง eq/gt/not แล้วรัน callback แค่ครั้งเดียวเพื่อจับ expression ที่เชื่อมต่อกัน จากนั้นแปลงเป็น IR
      น่าทึ่งตรงที่แม้แต่ js spread operator ก็ยัง trace ได้สำเร็จ
      Kenton อยากรู้ว่าพอจะเพิ่ม fake operator (eq, gt, in เป็นต้น) ให้ capnweb เพื่อใส่ความสามารถด้าน remote tracing ได้ไหม

    • ดูเหมือนว่าจะห้ามใช้ conditional (คล้ายกฎของ React hooks) เลยอยากรู้ว่าเขาบังคับข้อจำกัดนี้อย่างไร

  • โปรเจกต์นี้น่าสนใจมาก
    มันมีส่วนที่คล้ายกับไลบรารี ML compiler (TensorFlow 1, JAX jit, PyTorch compile เป็นต้น) คือใช้การ tracing เพื่อสร้าง operation graph แล้ว compile หรือแปลงให้เหมาะกับ VM เพื่อรัน
    ตอนนี้เราใช้ภาษาที่เป็น dynamic เป็น frontend โดยไม่ต้องนิยาม DSL ใหม่ แต่ซ่อนการแปลง AST ไว้ในภาษาสคริปต์เดิม
    ฝั่ง ML จะหน่วงการรัน GPU/linalg kernel เพื่อรวม kernel เข้าด้วยกัน ส่วน RPC อย่าง Cap'n Web ก็สามารถหน่วง network request เพื่อรวมหลาย network call เข้าด้วยกันได้
    สุดท้ายหัวใจคือการแยก instruction/data plane และแม้แต่ single CPU ที่เล็กมากก็ยังมีโครงสร้างแบบ distributed system (แยก command/data cache)
    ใน Cap'n Web นั้น RPC graph เองทำหน้าที่เป็น instruction
    รูปแบบนี้น่าสนใจจริง ๆ แต่ก็ให้ความรู้สึกเหมือนโครงสร้างแบบซ้อนชั้น (compiler บน interpreter, interpreter บน compiler...) วนซ้ำไม่รู้จบ เหมือนเป็นอีกเวอร์ชันของแนวคิดแบบ Lispy ที่ code is data, data is code รู้สึกว่าน่าจะมีเรื่องที่ลึกกว่านี้อยู่เบื้องหลัง

    • เห็นด้วยมาก — มุมมองที่เห็นสิ่งนี้เป็น abstraction แบบสากลนั้นยอดเยี่ยม
      ตอนนี้ภาษาแบบ dynamic กลายเป็น frontend สำหรับ DSL ใหม่ ๆ โดยไม่ต้องกำหนดไวยากรณ์ใหม่ แต่ฝังการสร้าง AST ลงไปในสคริปต์แทน
      ผมคิดว่า TypeScript เป็น game changer ในจุดนี้ เพราะช่วยให้ได้ทั้งความยืดหยุ่นของ JavaScript runtime (อย่างที่ Cap'n Web ใช้ Proxy อย่างชาญฉลาด) และ type safety ไปพร้อมกัน
      ช่วงนี้ผมอินกับแนวคิดนี้ในฝั่ง ORM มาก ORM ส่วนใหญ่มีลักษณะเป็นแบบ serial และ eager เลยปรับแต่งได้แค่ก่อน query จะ execute เท่านั้น
      ผมคิดว่า ORM ที่ composable จริง ๆ ควรทำงานเหมือน compiler: นิยาม DSL ที่ type-safe บน SQL ด้วย TypeScript เพื่อสร้าง query AST แล้วค่อย compile เป็น SQL ในตอนสุดท้าย
      Typegres ที่ผมกำลังพัฒนาก็ใช้แนวคิดนี้ตรง ๆ ถ้าสนใจแพตเทิร์นแบบนี้ก็น่าจะลองดูได้
  • ปัญหาหลักของไลบรารี RPC คือมันพยายามซ่อนว่าการ round-trip เกิดขึ้นที่ไหนและอย่างไร
    แค่ดู .map() ของ array ใน Cap'n Web ก็แทบไม่รู้แล้วว่า network round-trip เกิดตรงไหนจริง ๆ
    ผมคิดว่านี่ไม่ใช่ “ฟีเจอร์” แต่เป็น “บั๊ก” มากกว่า — มองโค้ดแล้วควรเข้าใจพฤติกรรมได้ทันที การปกปิดสิ่งนี้ไม่ใช่เรื่องที่ดี
    ลิงก์อ้างอิง

    • round-trip จะเกิดตอนใช้ await
      promise pipelining ทำให้สามารถตั้งหลาย statement ต่อกันได้โดยไม่ต้อง await ระหว่างทาง จึงไม่มี network round-trip เพิ่มเติมระหว่างนั้น พอ await ครั้งสุดท้ายครั้งเดียวก็จบทั้งหมด
  • ถ้าเคยใช้ gRPC กับเว็บมาก่อนจะรู้ว่าการเอา Protobuf มาใช้บนเว็บนั้นเจ็บปวดแค่ไหน
    ความเรียบง่ายของ Cap'n Web ดีมากจริง ๆ เอกสาร capnproto
    Cap'n Web ไม่มี schema เลยต่างจาก Cap'n Proto ทำให้แทบไม่มี boilerplate ที่ไม่จำเป็น และให้ความรู้สึกคล้าย JavaScript-native RPC ของ Cloudflare Workers มาก
    อ้างอิง github

  • เจอไลบรารีใหม่ของ kentonv แล้วรีบเข้ามาทันที
    พอดูโค้ดบน GitHub แล้วแปลกใจที่ขนาดเล็กมาก เลยสงสัยว่านี่คือทั้งหมดจริงหรือเปล่า
    ในทางทฤษฎีน่าจะพอร์ตฝั่ง server ไปภาษาอื่นได้ไม่ยากนัก ผมเลยอยากลองใช้กับ server ที่เป็น Elixir และ frontend ที่เป็น JS/TS
    ให้ LLM ช่วยพอร์ตภาษาแบบนี้ก็ดูน่าสนุกเหมือนกัน สงสัยว่าใน repo นี้มีโค้ดที่สร้างด้วย LLM อยู่ไหม เคยเห็น kentonv พูดเมื่อไม่กี่เดือนก่อนว่าเคยทำ POC ที่ AI สร้าง (และมีคนตรวจทาน) ไว้

    • มีบางส่วนของ test ที่ใช้ LLM สร้าง แต่ตัวไลบรารีหลักไม่ได้ใช้เลย
      ณ ตอนนี้ คิดว่า LLM คงยังสร้างไลบรารีนี้ไม่ได้ โครงสร้างภายในถูกออกแบบเหมือนปริศนาที่ชิ้นส่วนทุกชิ้นประกบกันอย่างประณีต
      เวลาส่วนใหญ่หมดไปกับการคิดเรื่องการออกแบบมากกว่าการเขียนโค้ดจริง
      มันต่างจากไลบรารี workers-oauth-provider ซึ่งเป็นการนำ spec ที่รู้จักกันดีมา implement ในแบบใหม่โดยสิ้นเชิง
      โครงสร้างโค้ดอาจย้ายไปภาษาที่ dynamic อย่าง Python ได้ง่าย แต่คิดว่าน่าจะยากในภาษาที่มี static type เพราะมีหลายส่วนที่พึ่งพา object type แบบ arbitrary
  • มีทั้งจุดที่คล้ายและจุดต่างสำคัญจาก OCapN อ้างอิง
    ทั้งคู่รองรับ capability transfer, promise pipelining และโมเดลแบบ schemaless
    Cap'n Web ไม่มี out-of-band capability แบบ sturdyref (URI ที่กู้คืนได้) ของ OCapN ด้วยเหตุนี้จึงเดาว่าต้องใช้การยืนยันตัวตนแบบ API key โดย sturdyref เป็นเสมือนโทเคนที่คาดเดาไม่ได้ และใครถืออยู่ก็เข้าถึง endpoint นั้นได้
    นอกจากนี้ Cap'n Web ยังไม่มีความสามารถด้านการ handoff แบบสามฝ่าย ที่ Alice แนะนำ Bob ให้ Carol ได้ ซึ่งเป็นสิ่งจำเป็นสำหรับแอปแบบ distributed ดังนั้น Cap'n Web จึงใกล้เคียงกับบริการแบบ client-server สไตล์ SaaS ดั้งเดิมที่หยิบเอาคุณสมบัติของ ocap มาใช้มากกว่า

    • อยากเพิ่มการรองรับ 3PH ในภายหลังอยู่เหมือนกัน แต่สำหรับรีลีสแรกนี้ สิ่งสำคัญกว่าคือการโฟกัสไปที่การสื่อสารระหว่าง browser <-> web server
      สำหรับ SturdyRef ผมคิดว่าการกู้คืนควรทำตามวิธีที่เหมาะกับแต่ละแพลตฟอร์ม มากกว่าจะพยายามใส่ไว้ในระดับ RPC protocol
      ตัวอย่างเช่น ใน Cloudflare Workers อีกไม่นานจะสามารถทำ capability persistence จาก Durable Object storage ได้ แต่รูปแบบ implementation ก็เฉพาะกับแพลตฟอร์ม worker
      Sandstorm ก็มี persistent capability เช่นกัน แต่จำกัดอยู่ภายในบริการของตัวเอง
      เพราะแบบนี้จึงถอดแนวคิด persistent capability ออกจาก Cap’n Proto ไปเลย และแนวคิดที่ใกล้เคียงที่สุดในมาตรฐานเว็บก็คือ OAuth
      จะจินตนาการถึง sturdyref ที่อิงกับ OAuth refresh token ก็ได้ แต่ก็ไม่ใช่โครงสร้างที่ใช้ได้กับทุกแพลตฟอร์ม
  • จากที่ดูคร่าว ๆ ระบบนี้ดูเหมือนจะต้องการ (หรืออย่างน้อยก็ส่งเสริม) ให้เก็บ import/export table หรือ object state แบบมี state ไว้ฝั่ง server
    RPC แบบดั้งเดิมนั้นทุก call จะเข้าที่ top-level และส่ง key ต่าง ๆ ไปกับแต่ละครั้ง จึงไม่มีปัญหาแม้คำขอจะถูกกระจายไปหลาย server แต่ Cap’n Web ไม่ได้เป็นแบบนั้น
    เลยสงสัยว่าสามารถ serialize table แล้วเก็บลง DB เพื่อกระจายโหลดข้าม server แบบเดิมได้หรือไม่ หรือจริง ๆ แล้วจำเป็นต้องใช้ server affinity หรือโครงสร้างอย่าง Durable Objects

    • state จะคงอยู่เฉพาะภายใน RPC session เดียวเท่านั้น
      ถ้าใช้ WebSocket state ก็จะอยู่ตราบเท่าที่การเชื่อมต่อ WebSocket ยังไม่หลุด
      ถ้าใช้การส่งแบบ HTTP batch session จะจำกัดอยู่ในคำขอ HTTP เดียวนั้นทั้งก้อน และ call ทั้งหมดจะถูกประมวลผลพร้อมกันในครั้งเดียว
      ดังนั้น Cap’n Web จึงไม่จำเป็นต้องรักษา state ข้ามหลายคำขอ HTTP/การเชื่อมต่อ
      อย่างไรก็ตาม ถ้าออกแบบจนเมื่อ session หลุดแล้วสูญเสีย capability ทั้งหมด ก็ถือว่าเป็นดีไซน์ที่ควรหลีกเลี่ยง ต้องทำให้สามารถรีเซ็ตการเชื่อมต่อและกู้คืน capability ได้เสมอ

    • จากเอกสารที่อ่าน ดูเหมือนจะใช้โครงสร้างที่อาศัย affinity ผ่าน websocket
      ส่วน http batching เป็นการส่งทุก request ไปพร้อมกันแล้วรอ response
      วิธีแบบนี้ทำให้การทำ load balancing ยากขึ้น ถ้ามี client แชตจำนวนมาก การเชื่อมต่ออาจกระจุกอยู่ที่ server บางตัว ซึ่งเสี่ยงทำให้ server นั้น overload
      การ scale in/out ฝั่ง server ก็จะยุ่งยากขึ้น เมื่อมีทั้งการรักษาการเชื่อมต่อระยะยาวและคำขอหลายรายการกำลังถูกประมวลผลพร้อมกัน การจัดการจะยากมาก
      อีกเรื่องหนึ่งคือ ถ้า client ส่ง push event มาเรื่อย ๆ โดยไม่รับ response เลย server ก็ต้องเก็บ response เหล่านั้นไว้ในหน่วยความจำต่อไป ทำให้มองว่าการโจมตีแบบ DDoS ทำได้ง่าย

    • จากที่เคยอ่านเอกสาร Cap'n Proto server และ client สามารถส่ง peer stub ให้กันได้
      ถ้า server C ได้รับ stub ที่สร้างจาก A ผ่าน client B, C ก็สามารถเรียก A ได้โดยตรงเช่นกัน

  • RPC is often accused of committing many of the fallacies of distributed computing.

But this reputation is outdated. When RPC was first invented some 40 years ago, async programming barely existed. We did not have Promises, much less async and await. อ่านตรงนี้แล้วงงนิดหน่อย ถ้าสมมติฐานหลักของ RPC ขึ้นอยู่กับภาษาใดภาษาหนึ่งหรือโมเดล concurrency แบบใดแบบหนึ่งมากขนาดนี้ มันจะยังถือเป็น protocol ได้อย่างไร

  • เดิมที “RPC” คือกระบวนทัศน์การเขียนโปรแกรมที่พยายามทำให้การเรียกจากระยะไกลดูแยกไม่ออกจากการเรียกฟังก์ชันภายในเครื่อง
    แน่นอนว่าเพื่อให้เกิดสิ่งนี้ขึ้นจริง ก็ต้องมี wire protocol และไลบรารีฝั่ง client/server รองรับ
    ทุกวันนี้ภาพจำเปลี่ยนไปมากแล้ว และแนวทางที่พบได้บ่อยคือมันดูคล้าย REST endpoint ที่มี function signature
    เมื่อภาษาโปรแกรมเริ่มมีฟีเจอร์อย่าง Future, Optional เป็นต้น เราก็สามารถแยกให้ชัดได้ว่า “การทำงานนี้อาจล่าช้า” หรือ “อาจล้มเหลว”
    RPC สมัยก่อนซ่อนคุณสมบัติเหล่านี้ไว้ทั้งหมด

  • อยากรู้ว่าเขาหมายถึงอะไร การเขียนโปรแกรมแบบ asynchronous มีอยู่ในหลายภาษา ผมเคยใช้ทั้ง JavaScript, C++, Python, Rust, C# และอีกหลายตัว
    ประเด็นคือ RPC ยุคแรก ๆ ถูกออกแบบให้ block calling thread ระหว่างรอ network request ซึ่งเป็นการออกแบบที่แย่มาก เลยทำให้ทุกวันนี้แนวคิดแบบ async กลายเป็นเรื่องปกติ

  • ดีใจมากที่ Cap'n Web ไม่ได้ผูกติดกับผลิตภัณฑ์ Cloudflare เท่านั้นแต่มีอยู่แยกต่างหากด้วย
    อ่าน ส่วนนี้ ในเอกสารแล้วมีคำถาม

    as of this writing, the feature set is not exactly the same between the two. We aim to fix this over time, by adding missing features to both sides until they match. ถ้าทั้งสองฝั่งไปถึง feature parity แล้ว หลังจากนั้นก็ยังตั้งใจจะรักษาให้ sync กันต่อไปไหม หรือสุดท้าย Cap'n Web จะค่อย ๆ ตามหลัง cloudflare workers และช่องว่างนั้นจะมากแค่ไหน

    • แผนคือทั้งสองผลิตภัณฑ์จะยังคง sync กันต่อไปเกือบทั้งหมด อย่างน้อยในส่วนของฟีเจอร์ที่มีความหมายร่วมกัน
      จริง ๆ แล้วผมคิดว่า Cap'n Web อาจไปไกลกว่า worker RPC ได้ด้วยซ้ำ (ตอนนี้ก็ล้ำกว่าแล้วในด้าน pipeline)
      โครงสร้างของ Cap'n Web เรียบง่ายกว่ามาก ดังนั้นการทดลองฟีเจอร์ใหม่ ๆ ก็น่าจะเกิดที่ Cap'n Web ก่อน