- หากสร้างเซิร์ฟเวอร์ ActivityPub เอง มักจะติดตั้งแต่คำขอ
Followแรกด้วย401 Unauthorizedที่ไม่มีคำอธิบาย และ Fedify คือเฟรมเวิร์ก TypeScript ที่ย้ายภาระเรื่อง ลายเซ็น, JSON-LD, การส่งต่อ และความปลอดภัย ออกไปไว้นอกโค้ดแอปพลิเคชัน - การยืนยันตัวตนใน fediverse ใช้ทั้งร่างที่หมดอายุ
draft-cavage-http-signatures-12และมาตรฐานRFC 9421ควบคู่กันไป และหากรวมลายเซ็นระดับเอกสารด้วย ก็ต้องรับมือกับ กลไกลายเซ็นสี่แบบ รวมถึงคีย์ RSA และ Ed25519 - แม้จะเป็นกิจกรรม ActivityPub เดียวกัน แต่ใน JSON-LD ก็อาจมาได้หลายรูปแบบ เช่น สตริง อาร์เรย์ อ็อบเจ็กต์แบบอินไลน์ หรือการอ้างอิง URI ทำให้ยิ่งเขียนเองมากเท่าไร โค้ดเชิงป้องกัน ก็ยิ่งกระจายไปทั่วทั้งโค้ดเบส
- ในการส่งต่อแบบกระจาย อาจเกิดปัญหาอย่าง “โพสต์ซอมบี้” ที่
Deleteมาถึงก่อนCreateจึงต้องมีคิว การลองใหม่ ความเป็น idempotent การรับประกันลำดับ และ circuit breaker - Fedify มีการเชื่อมต่อกับเว็บเฟรมเวิร์ก 13 แบบ, อะแดปเตอร์ KV และ message queue, รวมถึง CLI, linter, debugger และ OpenTelemetry ทำให้เริ่มพัฒนาแอปแบบ federated ได้ โดยไม่ต้องมีความรู้เชิงลึกเรื่อง ActivityPub
ปัญหาที่เจอเมื่อทำ ActivityPub เองโดยตรง
- หากจะส่งกิจกรรม
Followแรกไปยัง Mastodon ต้องจัดการทั้งการเขียน JSON, เซ็นคำขอ HTTP และPOSTแต่ถ้าล้มเหลว อาจได้กลับมาเพียงบรรทัดเดียวว่า401 Unauthorized- สาเหตุอาจเป็นความคลาดเคลื่อนของเวลาในเฮดเดอร์
Date, ความผิดพลาดของแฮชDigest, ตัวพิมพ์เล็กใหญ่ของ(request-target), วิธีแสดงคีย์สาธารณะ เป็นต้น - ถ้าเซิร์ฟเวอร์ปลายทางไม่บอกเหตุผล ก็อาจต้องไปอ่านโค้ดของเซิร์ฟเวอร์อื่นเพื่อดีบัก
- สาเหตุอาจเป็นความคลาดเคลื่อนของเวลาในเฮดเดอร์
- Fedify เริ่มต้นมาจากกระบวนการสร้าง Hollo ซึ่งเป็นเซิร์ฟเวอร์ไมโครบล็อกแบบผู้ใช้เดี่ยว
- เมื่อภาระในการทำ ActivityPub กลืนงานพัฒนาผลิตภัณฑ์ จึงถูกทำออกมาเป็นเฟรมเวิร์กที่จำเป็นก่อนตัวแอป
- ความยากหลัก ๆ กระจุกอยู่ที่ มาตรฐานลายเซ็น, รูปแบบเอกสาร JSON-LD, การส่งต่อแบบกระจาย, ธรรมเนียมเฉพาะของแต่ละ implementation และการตั้งค่าความปลอดภัยพื้นฐาน
มาตรฐานลายเซ็นไม่ได้มีแค่อย่างเดียว
- การยืนยันตัวตนระหว่างเซิร์ฟเวอร์ใช้ HTTP signature แต่ใน fediverse จริงมีทั้งร่างที่หมดอายุ draft-cavage-http-signatures-12 และมาตรฐาน RFC 9421 อยู่ร่วมกัน
- ไม่สามารถรู้ล่วงหน้าได้ว่าเซิร์ฟเวอร์ไหนรับลายเซ็นแบบใด จึงต้องลองเซ็นด้วยวิธีหนึ่งก่อน ถ้าถูกปฏิเสธก็ต้องเซ็นใหม่อีกแบบ และจดจำวิธีที่สำเร็จแยกตามแต่ละเซิร์ฟเวอร์
- ขั้นตอนนี้เรียกว่า double-knocking
- HTTP signature พิสูจน์ได้เพียงผู้ส่งคำขอ ดังนั้นในกรณีอย่าง inbox forwarding ที่ต้องส่งต่อกิจกรรมที่ได้รับไปยังบุคคลที่สาม ก็จำเป็นต้องมีลายเซ็นที่ติดกับตัวเอกสารด้วย
- กลไกที่ต้องใช้มีทั้งหมดสี่แบบ รวมถึง Linked Data Signatures และ Object Integrity Proofs
- ต้องจัดการคีย์ทั้ง RSA และ Ed25519 ควบคู่กันไป
รูปแบบเอกสาร JSON-LD เปลี่ยนไปตลอด
- รูปแบบการส่งของ ActivityPub คือ JSON-LD และแม้จะเป็นกิจกรรม
Createที่มีความหมายเดียวกัน ก็สามารถแสดงออกมาได้หลายแบบactorอาจเป็นสตริง URI หรือเป็นอ็อบเจ็กต์Personแบบอินไลน์ก็ได้toอาจเป็นสตริงตัวเดียว หรือเป็นอาร์เรย์ก็ได้objectอาจเป็นได้ทั้งอ็อบเจ็กต์แบบอินไลน์หรือ URI
- ที่อยู่สำหรับระบุการเผยแพร่สาธารณะก็ใช้ได้ทั้งสามแบบคือ
https://www.w3.org/ns/activitystreams#Public,as:Public,Public - หากจะประมวลผลให้ตรงตามสเปก ต้องทำ normalization โดยขยาย (expansion) แล้วบีบอัด (compaction) ผ่านตัวประมวลผล JSON-LD
- หลาย implementation กลับปฏิบัติกับมันเหมือนเป็น “แค่ JSON” จนพังแบบเงียบ ๆ เมื่อเจอรูปแบบที่เซิร์ฟเวอร์บางตัวส่งออกมา
- ถ้าทำเองโดยตรง จะมี โค้ดเชิงป้องกัน โผล่เต็มไปหมดเพื่อตรวจสอบว่าค่าเป็นสตริง อาร์เรย์ อ็อบเจ็กต์ หรือเป็น URI ที่ต้องไปดึงมา
การส่งต่อแบบกระจายและ “โพสต์ซอมบี้”
- ถ้าผู้ใช้โพสต์ข้อความแล้วเห็นพิมพ์ผิดจึงลบทันที เซิร์ฟเวอร์จะส่ง
Createตามด้วยDeleteแต่ด้วยสภาพเครือข่าย เซิร์ฟเวอร์ผู้รับอาจได้รับDeleteก่อน- ถ้ามองข้ามการลบของโพสต์ที่ยังไม่มีอยู่ แล้วมาประมวลผล
Createทีหลัง โพสต์ที่ผู้เขียนเชื่อว่าลบไปแล้วก็อาจยังคงอยู่บนเซิร์ฟเวอร์นั้นต่อไป
- ถ้ามองข้ามการลบของโพสต์ที่ยังไม่มีอยู่ แล้วมาประมวลผล
- หากมีผู้ติดตาม 5,000 คน โพสต์หนึ่งครั้งอาจสร้างการส่ง HTTP หลายพันรายการ และหากจัดการทั้งหมดในตัว request handler การตอบสนองของปุ่มโพสต์อาจช้าลงหรือถึงขั้นทำให้เซิร์ฟเวอร์ล่มได้
- ต่อให้ใช้คิว ก็ยังต้องกำหนดตารางการลองใหม่ของการส่งที่ล้มเหลว, exponential backoff, จำนวนครั้งที่ลองใหม่, ความแตกต่างระหว่าง
500 Internal Server Errorกับ410 Gone, การล้างรายชื่อผู้ติดตามของเซิร์ฟเวอร์ที่หายไป และการจัดการโฮสต์ที่ขัดข้องยาวนาน - พื้นที่นี้ใกล้เคียงกับงาน วิศวกรรมระบบกระจาย มากกว่าการทำโปรโตคอลแบบตรงไปตรงมา
แค่ทำตามสเปกก็ยังไม่จบเรื่องการทำงานร่วมกัน
- ต่อให้ทำตามสเปกอย่างสมบูรณ์ ปัญหาการทำงานร่วมกับ implementation จริงใน fediverse ก็ยังคงอยู่
- secure mode ของ Mastodon ใช้ authorized fetch ที่บังคับ HTTP signature แม้กับคำขอ
GET- หากเซิร์ฟเวอร์ทั้งสองฝั่งเปิด secure mode เหมือนกัน จะเกิดภาวะชะงักงันที่การดึงคีย์สาธารณะของอีกฝ่ายต้องใช้ลายเซ็น แต่การตรวจสอบลายเซ็นนั้นก็ต้องให้อีกฝ่ายดึงคีย์สาธารณะของเราก่อน
- ชุมชนจึงหลบปัญหาด้วยการเซ็นผ่าน instance actor ที่แทนตัวเซิร์ฟเวอร์เอง แต่สิ่งนี้ไม่ได้อยู่ในสเปก
- Threads ไม่สามารถพาร์สกิจกรรมที่
actorมาเป็นอ็อบเจ็กต์แบบอินไลน์ได้ ดังนั้นเวลาส่งไปยัง Threads ต้องส่งactorเป็น URI - Lemmy จะปฏิเสธแบบเงียบ ๆ หากไม่มีฟิลด์ของ
Groupactor ที่ Mastodon ไม่ได้บังคับ- ตัวอย่างเช่น moderators collection และ
featuredcollection ที่เชื่อมผ่านattributedTo
- ตัวอย่างเช่น moderators collection และ
- Misskey มีการขยาย vocabulary ของตัวเอง และแค่เรื่อง quote post ก็มีการใช้ชื่อพร็อพเพอร์ตี้อยู่สามแบบตามแต่ละ implementation
- การทำงานร่วมกันไม่ใช่งานที่ปรับครั้งเดียวแล้วจบ แต่เป็นเรื่องที่ต้องคอยดูแลต่อเนื่อง
สถานะตั้งต้นของการทำเองนั้นไม่ปลอดภัย
- หากข้ามการตรวจสอบลายเซ็นของกิจกรรมขาเข้า ใครก็สามารถใส่
FollowหรือDeleteปลอมเข้ามาได้ - หากไม่จำกัด document loader กิจกรรมอันตรายอาจชี้ไปที่
http://169.254.169.254/หรือเครือข่ายภายใน ทำให้เซิร์ฟเวอร์กลายเป็นพร็อกซี SSRF - หากละเลยการตรวจสอบแหล่งที่มาของ embedded object เซิร์ฟเวอร์ใด ๆ ก็สามารถปล่อยเอกสารที่ทำให้ดูเหมือนบุคคลใดบุคคลหนึ่งเป็นคนพูดได้
- กับดักเหล่านี้มักไม่เห็นชัดในทันที และก่อนจะถูกนำไปใช้โจมตี ทุกอย่างอาจดูเหมือนทำงานได้ตามปกติ
สิ่งที่ Fedify จัดการแทน
- Fedify เป็นไลบรารี TypeScript สำหรับสร้างแอปเซิร์ฟเวอร์แบบเฟเดอเรตด้วย ActivityPub และมาตรฐานที่เกี่ยวข้อง
- ทำงานได้บน Deno, Node.js และ Bun รวมถึงรองรับ edge runtime อย่าง Cloudflare Workers
- เป้าหมายของการออกแบบคือย้ายเรื่องลายเซ็น, JSON-LD, การส่งต่อ, ความต่างระหว่างอิมพลีเมนเทชัน และรายละเอียดด้านความปลอดภัย ออกจากโค้ดแอปพลิเคชัน
-
การจัดการลายเซ็น
- เมื่อลงทะเบียน actor dispatcher และ key pair dispatcher ก็สามารถเผยแพร่ actor หนึ่งตัวขึ้นสู่ fediverse ได้
- ทุกคำขอขาออกจะมีลายเซ็นแนบไปด้วย
- สำหรับคีย์ RSA จะสร้างทั้ง HTTP Signatures และ Linked Data Signatures
- หากเพิ่มคีย์ Ed25519 ก็จะมี Object Integrity Proofs ติดไปด้วย
- กลไกทั้งสี่แบบสามารถอยู่ร่วมกันใน activity เดียว และผู้รับจะตรวจสอบด้วยวิธีที่แข็งแรงที่สุดที่ตนเข้าใจ
- Fedify จัดการ double-knocking ให้โดยตรง
- การติดต่อครั้งแรกจะส่งด้วย RFC 9421 และถ้าถูกปฏิเสธก็จะลองใหม่ด้วย draft-cavage
- วิธีที่สำเร็จจะถูกแคชแยกตามแต่ละเซิร์ฟเวอร์
- หากการตอบกลับแบบปฏิเสธมี
Accept-Signaturechallenge ก็จะเซ็นใหม่ตามองค์ประกอบที่เซิร์ฟเวอร์ร้องขอ
- ลายเซ็นขาเข้า จะถูกตรวจสอบก่อนที่โค้ดแอปพลิเคชันจะได้เห็น และ activity ที่ตรวจสอบไม่ผ่านจะไปไม่ถึง listener
- แค่ลงทะเบียน actor dispatcher ก็จะได้เซิร์ฟเวอร์ WebFinger RFC 7033 มาด้วย ทำให้ค้นหา actor ในช่องค้นหาของ Mastodon ได้ด้วยรูปแบบ
@alice@example.com
-
จัดการ type แทน JSON-LD
- Fedify มี คลาสราว 80 คลาส ที่ครอบคลุม Activity Vocabulary ทั้งชุดและส่วนขยายหลักจากผู้ให้บริการต่าง ๆ
- คลาสเหล่านี้มี type, immutable และ accessor จะช่วยซ่อนความต่างของรูปแบบเอกสารที่ JSON-LD อนุญาตไว้
lookupObject()รับ handle แล้วทำขั้นตอน lookup ทั้งหมด รวมถึง WebFinger discovery- accessor อย่างเช่น
getFollowers()ทำงานได้เหมือนกันไม่ว่าค่าจะเป็น URI reference หรือ inline object และค่าที่ดึงมาแล้วจะถูกแคช - ความต่างเฉพาะของแต่ละผู้ให้บริการก็ถูกซ่อนไว้หลัง API เช่นกัน
- คุณสมบัติ quote ทั้งสามแบบคือ
quoteUri,_misskey_quote,quoteUrlถูกหลอมรวมไว้หลัง API เดียวร่วมกับquoteของ FEP-044f ที่เพิ่ง появขึ้นใหม่ - คุณสมบัติ
isCatของ Misskey ก็มีเป็น type เช่นกัน จึงจัดการได้อย่าง type-safe
- คุณสมบัติ quote ทั้งสามแบบคือ
-
โครงสร้างพื้นฐานการส่งต่อและการรับประกันลำดับ
- หากเชื่อม message queue เข้ากับ
createFederation()การส่งต่อจะย้ายไปทำงานเบื้องหลัง และเมื่อเกิดความล้มเหลวจะมีการ retry อัตโนมัติด้วย exponential backoff โดยค่าปริยายสูงสุด 10 ครั้ง - เมื่อโพสต์หนึ่งชิ้นต้องส่งต่อถึงผู้ติดตามหลายพันคน ระบบ two-stage fan-out จะทำงาน
- ข้อความรวมหนึ่งรายการจะถูกใส่เข้าคิว
- worker เบื้องหลังจะแยกมันออกเป็นงานส่งต่อรายเซิร์ฟเวอร์
- ปุ่มโพสต์จะตอบกลับได้ทันที
- เพราะการ retry อาจทำให้ activity เดิมมาถึงสองครั้ง Fedify จึงข้ามรายการซ้ำก่อนถึง handler ด้วย idempotency cache ที่เก็บ activity ที่ประมวลผลแล้วไว้ 24 ชั่วโมง
- หากระบุ
{ orderingKey: post.id }ในการเรียกsendActivity()activity ที่ใช้ orderingKey เดียวกันจะถูกส่งถึงแต่ละเซิร์ฟเวอร์ปลายทางตามลำดับที่ส่งDeleteจะไม่แซงCreate- activity ที่ใช้คีย์ต่างกันจะถูกส่งแบบขนานเพื่อคง throughput ไว้
- เมื่อเจอ
404 Not Foundหรือ410 Goneระบบจะหยุด retry และเรียก permanent delivery failure handler ที่ลงทะเบียนไว้ - หากส่งไปยัง shared inbox ก็ยังสามารถรับรายชื่อผู้ติดตามที่อยู่เบื้องหลังนั้นมาใช้ทำความสะอาดบัญชีที่หายไปได้
- สำหรับโฮสต์ที่ล้มเหลวซ้ำ ๆ circuit breaker ที่เปิดใช้งานโดยปริยายจะพักการส่งต่อไว้และตรวจสอบการฟื้นตัวเป็นระยะ
- หากเชื่อม message queue เข้ากับ
แนวปฏิบัติเฉพาะของแต่ละอิมพลีเมนเทชันและค่าเริ่มต้นด้านความปลอดภัย
- Fedify รองรับ authorized fetch โดยเชื่อม
.authorize()เข้ากับ dispatcher เพื่อส่งตัวตนของผู้ขอที่ตรวจสอบแล้วเข้า callback- การจัดการอย่างรายการบล็อกหรือคอลเล็กชันส่วนตัวสามารถเขียนเป็นตรรกะแอปพลิเคชันได้
- มีแพตเทิร์นรองรับแม้กับปัญหา deadlock ของ instance actor
- ปัญหา inline actor ของ Threads ถูกจัดการด้วย activity transformer ที่เปิดใช้โดยปริยาย ซึ่งจะแปลง inline actor ใน activity ขาออกให้เป็น URI
- moderators collection ที่ Lemmy ต้องการสามารถเปิดเผยได้ในไม่กี่บรรทัดด้วย custom collection API และมี JSON-LD context ของ Lemmy รวมมาให้ล่วงหน้าแล้ว
- เมื่อพบปัญหา interoperability ใหม่ ๆ การแก้ไขจะเข้าไปที่ Fedify ไม่ใช่ในแต่ละแอปพลิเคชัน
- ค่าเริ่มต้นด้านความปลอดภัยเอนเอียงไปทางที่ปลอดภัยกว่า
- การตรวจสอบลายเซ็นไม่ใช่ฟีเจอร์ที่ต้องเปิด แต่เป็นฟีเจอร์ที่ปิดได้เพื่อการทดสอบ
- document loader จะบล็อก private address range และ loopback โดยปริยาย และยังคำนึงถึง DNS rebinding ด้วย
- หากจะเปิดให้เสี่ยงต่อ SSRF ต้องเปิดตัวเลือกที่ตั้งชื่อชัดเจนว่าใช้เพื่อการทดสอบโดยเจตนาเท่านั้น
- หากแหล่งที่มาของ embedded object ต่างจากเอกสารแม่ accessor จะไม่เชื่อถือและจะดึงใหม่จากต้นทาง
- โมเดลความปลอดภัยตามแหล่งที่มานี้อิงตาม FEP-fe34
สแตกเดิมและเครื่องมือพัฒนา
- Fedify ถูกออกแบบมาให้เข้ากับเว็บสแตกที่มีอยู่เดิม และมี การผสานรวมกับเว็บเฟรมเวิร์ก 13 รายการ
- Express, Hono, Fastify, Koa, NestJS, Elysia
- Next.js, Nuxt, SvelteKit, Astro, SolidStart, Fresh
- มิดเดิลแวร์จะจัดการ content negotiation ทำให้ URL เดียวกันสามารถส่ง HTML ให้เบราว์เซอร์ และส่ง JSON-LD ให้เฟดิเวิร์สได้
- สตอเรจของ Fedify เองต้องการเพียง อินเทอร์เฟซ key-value เดียว
- มีอะแดปเตอร์สำหรับ Redis, PostgreSQL, MySQL/MariaDB, SQLite, Deno KV, Cloudflare Workers KV และ in-memory
- message queue มีให้ 8 แบบ รวมถึง PostgreSQL, Redis, AMQP/RabbitMQ และหากไม่มีตัวที่เหมาะ ก็สามารถลงมือ implement อินเทอร์เฟซเองได้
- ข้อมูลโดเมนสามารถคงไว้ในฐานข้อมูลและ ORM เดิมได้ตามเดิม
- หากใช้งาน federation อยู่แล้วด้วยไลบรารีอื่น ก็สามารถย้ายมาจาก activitypub-express เป็นต้นได้โดยไม่สูญเสียผู้ติดตามเดิม ด้วย คู่มือย้ายระบบ และสคริปต์ migration ข้อมูล
- มีแพ็กเกจระดับบนให้ด้วย
@fedify/relayให้เซิร์ฟเวอร์ ActivityPub relay ที่สมบูรณ์ได้ด้วยการเรียกฟังก์ชันเพียงครั้งเดียว@fedify/backfillจะไล่ตามเฟดิเวิร์สเพื่อกู้คืนเธรดบทสนทนาที่ไม่สมบูรณ์
-
เครื่องมือสำหรับ development loop
fedify initใช้ scaffold โปรเจกต์ได้ในบรรทัดเดียวfedify tunnelเปิดเผยเซิร์ฟเวอร์โลคัลผ่าน HTTPS เพื่อให้ทดสอบกับ Mastodon จริงได้fedify inboxรันเซิร์ฟเวอร์ inbox ชั่วคราวไว้รับ activity ที่เซิร์ฟเวอร์ส่งมาfedify lookupใช้ตรวจสอบอ็อบเจ็กต์ที่โพสต์โดยเซิร์ฟเวอร์อื่นได้fedify lookup --authorized-fetchจะสร้างคีย์คู่แบบใช้ครั้งเดียวและตั้งเซิร์ฟเวอร์ ActivityPub ชั่วคราว เพื่อส่งคำขอที่มีลายเซ็นไปยังอ็อบเจ็กต์ที่อยู่หลัง secure mode@fedify/lintเป็นลินเตอร์เฉพาะ ActivityPub ที่จับบั๊กด้านการทำงานร่วมกันได้ 20 แบบ เช่น actor ที่ไม่มีinbox- สามารถรันทดสอบแบบไม่ใช้เครือข่ายได้ด้วย mock ของ
@fedify/testing @fedify/debuggerเพิ่มแดชบอร์ดดีบักได้ในบรรทัดเดียว ทำให้ดู activity และผลการตรวจสอบลายเซ็นแบบเรียลไทม์ในเบราว์เซอร์ได้- ในสภาพแวดล้อม production มี OpenTelemetry instrumentation ในตัว พร้อม 28 span type และ 37 metric
- ยังมี คู่มือมอนิเตอร์ริง และเครื่องมือทดสอบโหลดสำหรับ ActivityPub อย่าง
fedify bench - เอกสารทางการประกอบด้วยคู่มือ 30 บทและทิวทอเรียล 5 ชุด ครอบคลุมแนวปฏิบัติจริง เช่น PromQL query และกฎแจ้งเตือนสำหรับดู queue backlog รวมถึงพร็อพเพอร์ตีที่ทำให้อวาตาร์แสดงผลใน Mastodon
กรณีใช้งานจริงและวิธีเริ่มต้น
- Fedify ถูกใช้งานอยู่ในบริการจริง
- บริการ ActivityPub ของ Ghost
- Encyclia ที่เชื่อมบันทึกนักวิจัยของ ORCID เข้ากับเฟดิเวิร์ส
- SiliconBeest ที่ทำงานแบบ serverless บน Cloudflare Workers
- Typo Blue แพลตฟอร์มบล็อกของเกาหลี
- Hollo แพลตฟอร์มไมโครบล็อกแบบผู้ใช้คนเดียว
- Hackers' Pub ที่ขับเคลื่อนโดยชุมชน
- ทิวทอเรียลมีตัวอย่างตามขนาดงาน
- เซิร์ฟเวอร์ไฟล์เดียวขนาดไม่กี่สิบบรรทัด ที่ Mastodon สามารถติดตามได้
- บริการแชร์รูปภาพขนาดราว 750 บรรทัด ที่ทำงานร่วมกับ Pixelfed ได้ทั้งการติดตาม กดถูกใจ และคอมเมนต์
- ทิวทอเรียลแพลตฟอร์มชุมชน ที่ครอบคลุม federation แบบสองทางกับ lemmy.ml จริง
- เป้าหมายของ Fedify ไม่ใช่การสร้างผู้เชี่ยวชาญด้าน ActivityPub เพิ่มขึ้น แต่คือการทำให้นักพัฒนาสร้างแอปแบบ federated ได้โดยไม่ต้องรู้รายละเอียดปลีกย่อยของ ActivityPub
- คำสั่งเริ่มต้นคือ
npm init @fedify - หากต้องการความช่วยเหลือ สามารถใช้ Matrix room หรือ GitHub Discussions ได้
1 ความคิดเห็น
ความคิดเห็นจาก Lobste.rs
นี่แหละคือเหตุผลที่โปรเจกต์ ActivityPub มี fork ของกันและกันเยอะมาก: การทำความเข้าใจแนวทางของคนอื่นง่ายกว่าการลงมือ implement ทั้งหมดด้วยตัวเอง
สิ่งที่ผู้เขียนเสนอเองก็ดูไม่ได้ต่างจากพวก fork ของ Misskey หรือ Pleroma ที่เห็นกันบ่อยนักนัก ไลบรารีเองก็มีมุมมองและแนวทางของมันอยู่ และดูเหมือนจะไม่ได้ให้อำนาจควบคุมมากนัก แต่ข้อดีก็คืออย่างน้อยก็ไม่ได้บังคับไปถึง UI เหมือนตอน fork ทั้งเซิร์ฟเวอร์
ในฐานะคนที่กำลัง implement AP อยู่ ส่วนที่ยากที่สุดคือ ไม่มีวิธีที่ดีในการใช้ JSON-LD ให้ถูกต้องจริง ๆ ถ้าสามารถแปลงอ็อบเจ็กต์เป็น representation มาตรฐานได้ง่าย การทำงานร่วมกันก็น่าจะตามมาเอง แต่ถ้าจะใช้มันเหมือน linked document จริง ๆ ก็ไม่มีประสิทธิภาพเกินไป และถ้าใช้เหมือนเอกสาร JSON ดิบ ๆ ก็จะตายกับเคสยกเว้นมหาศาล จนถึงตอนนี้ผมเลือกแนวทางหลังแล้วก็เจ๊งมาแล้ว
ถึงจะไม่ใช่ปัญหาเดียวกับโลกของ JSON-LD แบบเป๊ะ ๆ แต่ก็ไม่ได้ไม่เกี่ยวกันเลย
อย่างไรก็ดี ผมคิดว่าเทคโนโลยีข้างเคียงของ JSON จำนวนมากก็เจอปัญหาคล้ายกัน มีวิธีทำ JSON Schema เยอะเกินไปสำหรับสคีมาเชิงตรรกะเดียวกัน และเพราะงั้นการทำงานกับเทคโนโลยีรอบ ๆ JSON Schema จึงน่ากลัวแบบขำไม่ออก โดยเฉพาะสคีมาของ OpenAPI ที่คล้ายแต่ไม่เหมือน เป็นหนังสยองขวัญอีกแบบหนึ่ง และต่อให้ยังไม่ต้องนับจำนวนเวอร์ชันร่างของสคีมา มันก็แย่มากพออยู่แล้ว
บริการ “MTA” ของ AP จะรับหน้าที่ส่งข้อความจาก outbox และรับข้อความเข้า inbox เอกสาร JSON-LD สำหรับบริการนี้แทบจะเป็นข้อมูลก้อน ๆ มากกว่า ต้อง parse นิดหน่อยเพื่อหาผู้ส่งกับผู้รับ แต่ไม่ได้ต้องทำมากกว่านั้นมากนัก storage ก็อาจเป็นแบบไฟล์ก็ได้ และถ้าจำไม่ผิด go-ap ก็ใช้แนวนี้
ส่วน “MUA” ของ AP ก็คือตัวแอปพลิเคชันจริง เป็นฝั่งที่ต้องเข้าใจความหมายของ JSON-LD อาจใช้ PostgreSQL เก็บเอกสารเป็น jsonb แล้วใช้ generated columns กับ views เพื่อให้เข้าถึงในรูปแบบที่เป็นมิตรกับ SQL มากขึ้น แบบนั้นก็จะกำหนดวิธีแทนเอกสารที่เหมาะที่สุดตามประเภทของอ็อบเจ็กต์ได้
อีกตัวอย่างหนึ่งคืออาจ model บริการค้นหาเป็น actor ด้วย แล้วให้มันส่งผลลัพธ์กลับมาทาง outbox ชั่วคราว
นี่เป็นรายการที่มีคุณค่ามาก เพราะรวบรวม พฤติกรรมประหลาด ของหลาย implementation และวิธีบรรเทาปัญหาไว้
น่าเสียดายที่ใน GoActivityPub ยัง implement ได้ไม่ถึงครึ่งของพวกนั้นเลย
ตอนต้นบทความเริ่มด้วยเรื่องเทคนิค เลยรู้สึกขอบคุณ แต่พอกลางเรื่องกลับเหมือนหักไปเป็นการ โปรโมตเฟรมเวิร์ก ของตัวเอง ทำให้อรรถรสในการอ่านตกลงไป
ก็ดีอยู่ที่อย่างน้อยในโลกบางส่วนที่ใช้ TypeScript อาจไม่ต้องมาเจอจุดพิสดารในการ implement พวกนี้ซ้ำอีก แต่ถ้ามีบันทึกเชิง mental model ว่า “ในเงื่อนไขและสถานการณ์นี้จะได้ผลลัพธ์แบบนี้ และต้องแก้แบบนี้” คนที่ไม่ได้ใช้ TypeScript เช่นผู้เขียนโปรเจกต์พี่น้องอย่าง GoActivityPub ก็จะได้อานิสงส์จากความลำบากนั้นด้วย ที่นี่มีพูดถึงบางอย่างอยู่บ้าง แต่บทความเป็นแค่ snapshot ของช่วงเวลาหนึ่ง ขณะที่ตัวโปรเจกต์ดูเหมือนจะค่อย ๆ สะสมบั๊กด้าน interoperability ทั้งหมดไปเรื่อย ๆ ตามกาลเวลา
ตอนนี้ทางเลือกที่ผมเห็นก็คือไล่อ่าน commit message ที่ไม่ได้เขียนโดยมนุษย์ ทั้งหมด เพื่อแยกให้ออกว่าอันไหนเป็นบั๊กของ Fedify เอง และอันไหนเป็นบั๊กด้าน interoperability
ยิ่งไปกว่านั้น ทั้งที่ repository นี้ดูเหมือนจะทุ่มสุดตัวให้ AI แต่กลับไม่ทำบัญชีแยกประเภทแบบนั้นก็ชวนให้รู้สึกย้อนแย้ง โฆษณาชวนเชื่อที่ได้ยินเกี่ยวกับ LLM คือมันช่วยทำงานจุกจิกซ้ำ ๆ ให้เป็นอัตโนมัติ ถ้าอย่างนั้นก็ให้ Claude สร้าง GitHub issue หรือดีกว่านั้นคือให้เขียนไฟล์ .md ใน repo เพื่อบันทึกสิ่งที่สังเกตพบและวิธีที่ Fedify ใช้แก้ก็ได้ ไหนจะมี debugger ของตัวเองอยู่แล้ว และยังมี “best practices” ที่ไม่รู้ว่าหมายถึงอะไร อีก แบบนี้ยิ่งเหมาะกับงานนี้มาก
ทำไมถึงไปทำ request ไปยังบริการภายนอกแบบ inline ล่ะ? นี่มันพื้นฐานของเว็บแอปพลิเคชันเลย ถ้าต้องคุยกับบริการภายนอกก็ควรส่งไปเป็นงานเบื้องหลัง ถ้าเป็นข้อมูลที่ไม่จำเป็นต่อการตอบ request ก็ยิ่งควรส่งไปเป็นงานเบื้องหลัง ปัญหาที่เกิดจากการทำ request แบบนี้ใน request handler เป็นปัญหาที่ก่อขึ้นเองระดับเหยียบคราดแล้วด้ามเด้งฟาดหน้า ไม่เกี่ยวอะไรกับ ActivityPub เลย
ถ้าการส่งล้มเหลวก็ต้อง retry จะจัดตารางยังไง ใช้ exponential backoff ไหม จะลองกี่ครั้ง จะนับ 500 Internal Server Error กับ 410 Gone เป็นความล้มเหลวแบบเดียวกันหรือเปล่า ทั้งหมดนี้ก็เป็นแค่ปัญหาทั่วไปของการพัฒนาเว็บแอปพลิเคชัน เป็นปัญหาที่เกิดเวลาให้ task queue ไปยิง request ไปยังบริการภายนอก และไม่เกี่ยวกับ ActivityPub เว็บเฟรมเวิร์กส่วนใหญ่มีค่าเริ่มต้นที่สมเหตุสมผลอยู่แล้ว จุดที่ต้องตัดสินจริง ๆ มีแค่ตอนที่ต้องเลือกว่าจะ retry หรือไม่ตามชนิดของ error การ retry 410 เป็นการสิ้นเปลือง แต่ก็ไม่ใช่ปัญหาเร่งด่วนอะไร มันอาจเพิ่มแรงกดดันด้านหน่วยความจำของ task queue แต่โอกาสที่จะทำให้แอปล่มภายในไม่กี่ชั่วโมงนั้นต่ำ
“ดูว่าถูกปฏิเสธไหม แล้วเซ็นใหม่ด้วยวิธีอื่น แล้วจำไว้ว่าต่อเซิร์ฟเวอร์วิธีไหนใช้ได้” นี่มันอ่านอะไรกันอยู่เนี่ย แบบนี้เองหรือที่ทำให้ การพัฒนา Mastodon ช้าขนาดนี้?
“โพสต์หนึ่งครั้งเกิดการส่ง HTTP หลายพันรายการ” เนี่ยนะ ทั้งที่พูดถึง Ruby ซึ่งขึ้นชื่อว่าเป็นภาษาที่เก่งเรื่องการเขียนโปรแกรมระบบเครือข่ายและคิว
ฟังดูไม่น่าเชื่อ และแม้จะดีที่เอาเรื่องนี้ไปห่อเป็นไลบรารีแล้ว แต่ก็ยังชวนอึ้งอยู่ดี
หลังจากเคย implement ActivityPub ด้วย Java ผมก็ได้ข้อสรุปว่า โปรโตคอลระหว่างเซิร์ฟเวอร์ แบบนี้ควรสร้างบน git ไปเลยดีกว่า
ความซับซ้อนส่วนใหญ่มีอยู่เพราะกำลังพยายามแก้ปัญหาที่ git แก้ไว้ดีกว่าอยู่แล้ว ถ้า model มันเป็นเอกสาร JSON ภายใน git repository ก็ไม่ต้องมายุ่งกับ pagination โปรโตคอลก็รับประกันได้อยู่แล้วว่าจะส่งเฉพาะข้อมูลที่ยังไม่มี ได้ทั้ง commit signing ได้ทั้งการรับประกันลำดับเหตุการณ์ แก้ปัญหาที่บทความนี้พูดถึงได้ และยังได้ประวัติฟรี ๆ อีกด้วย น่าจะตั้งกฎข้อที่สิบของ Greenspun เวอร์ชันหนึ่งได้ว่าโปรโตคอลแบบนี้มักมี git ครึ่ง ๆ กลาง ๆ ที่ทั้งช้าและมีบั๊กซ่อนอยู่ข้างใน
บทความนี้อ่านแล้วเหมือนเป็น บทความคุณภาพต่ำที่ AI สร้างขึ้น
ถ้าจะพูดให้เฉพาะกว่านั้นคือไม่เข้าใจว่าทำไมถึงเขียนเป็นรูปแบบเล่าเรื่อง ข้อเท็จจริงที่ต้องการสื่อสามารถเขียนได้กระชับกว่าและมีอคติน้อยกว่านี้มาก และตัวเรื่องเล่าก็ไม่น่าโน้มน้าวใจ โดยเฉพาะเพราะมีสำนวนแบบ AI อยู่เยอะ
อ่านแล้วไม่สนุก ถึงอย่างนั้นก็ยังขอบคุณที่ชี้ปัญหาต่าง ๆ ออกมา และหวังว่าจะได้อ่านและแก้ปัญหาเหล่านั้นในรูปแบบอื่น