Cap'n Web ระบบ RPC ใหม่สำหรับเบราว์เซอร์และเว็บเซิร์ฟเวอร์
(blog.cloudflare.com)- 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 ความคิดเห็น
ความคิดเห็นบน Hacker News
สงสัยอยู่สองเรื่อง
คิดว่านี่เป็นงานที่ล้ำมาก
ถ้ามี 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" หรือไม่ แล้วแปลงเป็น SQLWHEREclause ได้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 รู้สึกว่าน่าจะมีเรื่องที่ลึกกว่านี้อยู่เบื้องหลัง
ตอนนี้ภาษาแบบ 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 เกิดตรงไหนจริง ๆผมคิดว่านี่ไม่ใช่ “ฟีเจอร์” แต่เป็น “บั๊ก” มากกว่า — มองโค้ดแล้วควรเข้าใจพฤติกรรมได้ทันที การปกปิดสิ่งนี้ไม่ใช่เรื่องที่ดี
ลิงก์อ้างอิง
awaitpromise 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 สร้าง (และมีคนตรวจทาน) ไว้
ณ ตอนนี้ คิดว่า 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 มาใช้มากกว่า
สำหรับ 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” คือกระบวนทัศน์การเขียนโปรแกรมที่พยายามทำให้การเรียกจากระยะไกลดูแยกไม่ออกจากการเรียกฟังก์ชันภายในเครื่อง
แน่นอนว่าเพื่อให้เกิดสิ่งนี้ขึ้นจริง ก็ต้องมี 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 เท่านั้นแต่มีอยู่แยกต่างหากด้วย
อ่าน ส่วนนี้ ในเอกสารแล้วมีคำถาม
จริง ๆ แล้วผมคิดว่า Cap'n Web อาจไปไกลกว่า worker RPC ได้ด้วยซ้ำ (ตอนนี้ก็ล้ำกว่าแล้วในด้าน pipeline)
โครงสร้างของ Cap'n Web เรียบง่ายกว่ามาก ดังนั้นการทดลองฟีเจอร์ใหม่ ๆ ก็น่าจะเกิดที่ Cap'n Web ก่อน