Videos Social Engineering of making a framework

Chapters

  • แนะนำตัวและหัวข้อการพูด 0:00
  • แนะนำ Framework Elysia 0:30
  • อธิบายฟีเจอร์ End-to-End Type Safety 0:55
  • สาธิตการใช้งาน Elysia 1:56
  • การเพิ่ม Swagger UI 3:00
  • ปัญหาการ Sync Type ระหว่าง Frontend และ Backend 4:24
  • การใช้งาน End-to-End Type Safety 4:43
  • อธิบายการทำงานเบื้องหลังของ End-to-End Type Safety 6:36
  • การใช้ JavaScript Proxy ในการทำ End-to-End Type Safety 8:20
  • เทคนิคการ 'แก๊สไลท์' ผู้ใช้ในการเขียน API 10:09
  • ข้อจำกัดทางวิศวกรรมและวิธีการแก้ปัญหา 11:27
  • สรุปแนวคิดในการพัฒนา Framework 12:16

Transcript

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

แนะนำตัวและหัวข้อการพูด0:00

ปั๊บ เดี๋ยวขอเสียงปรบมือให้ออมนะครับผม กับหัวข้อ พูดชื่อเองแล้วกันนะครับ

ได้ครับ ก็วันนี้ไม่มี ขอบคุณมากครับ

เอ๊ย โอเค วันนี้ก็จะมาพูดเรื่องว่า ในการทำ framework อ่ะ มันมีสกิลต้องแบบ ต้องมีสกิลตอแหลนิดนึง ก็คือ Social Engineering นี่แหละ ก็คือ แนะนำตัวนิดนึงก็คือ

แนะนำ Framework Elysia0:30

ผมทำ framework ชื่อ Elysia คือตัวนี้ ตอนนี้มี อุ้ย เขิน มีดาวอยู่ประมาณ 8.5 ดาวมั้ง ประมาณ 8,500 นั่นแหละ ก็

เจมส์จับเวลาด้วย 8.5 นี่คือมี 8 คน แล้วอีกครึ่งคน ใช่ 8 คนแล้วอีกครึ่งคน ฮะ โอเค นั่นแหละ ก็ จะมาเล่านิดนึงว่า จริงๆแล้วเนี่ย

อธิบายฟีเจอร์ End-to-End Type Safety0:55

คือแบบ อันนี้มันเป็นเหมือน framework ในการทำแบบเว็บเซิร์ฟเวอร์เนาะ ก็ API มันจะคล้ายๆ Express นี่แหละ ทีนี้เนี่ย มันจะมีฟีเจอร์บางอัน ที่มันแบบค่อนข้างดูเป็นเมจิกนิดนึง ก็คือ อันนึงเรียกว่า End-to-End Type Safety ก็คือ เราเขียนโค้ดบนเซิร์ฟเวอร์อ่ะ หน้าตาเป็นอย่างงี้ ปึ๊บ อ่ะ เป็นปกติเลย แล้วเราก็ export type มา ทีนี้ เราย้ายไปอีก repo นึง มาที่หน้าบ้านเนาะ แล้วทีนี้ เราก็จะสามารถ import แค่ type มา ปึ๊บ เราก็จะสามารถ interact กับตัวเซิร์ฟเวอร์ได้ โดยที่แบบ ปกติจะต้องเขียน fetch เนาะ แต่ปัญหาก็คือ พอเราเขียน fetch เงี้ย สมมุติ endpoint เรา เขียน type มาเรียบร้อยละ อ่ะ เราก็ต้องมาเขียนหน้าบ้าน หลังบ้านแยกกันเนาะ เขียน type ทีนี้ สมมุติหลังบ้านเนี่ย

เค้าเกิดแบบเปลี่ยนใจว่า โปรเจคเมเนเจอร์ อยากให้แบบ เปลี่ยนฟิลด์ เพิ่มฟิลด์นู่น เพิ่มฟิลด์นี่ หรืออาจจะเปลี่ยน structure อะไรเงี้ย มันก็จะมีปัญหาว่า หน้าบ้านกับหลังบ้าน มันไม่ซิงค์กันเนาะ ก็แต่ฟีเจอร์เนี่ย มันสามารถทำให้เรา สามารถซิงค์ type กันได้ ก็คือ ถ้าคุณเขียนหน้าบ้านอย่างงี้อ่ะ เดี๋ยวเรา จะเอาเข้า code editor ให้ดูเลย ว่ามันทำงานยังไงเนาะ

ก็ก๊อปปี้นี่มา นะ ก็

สาธิตการใช้งาน Elysia1:56

วิธีการเริ่มเขียนเนาะ ง่ายมาก ก็คือ ปึ๊บ เปิด

ใหญ่สุดแล้วป่ะ ใหญ่สุดได้แค่นี้ โอเค ไม่เป็นไร ปึ๊บ เราก็ไป demo เนาะ ปึ๊บ เราก็รัน bun create elysia แล้วก็ชื่อโฟลเดอร์ ปึ๊บ ก็จะได้เป็น

ทำไม bun ช้าจัง โอ้ยมันไม่มีแคช ไม่ได้ลงมานานแล้วปุ๊บเราก็จะได้ฟีเจอร์ Elysia มาปุ๊บรันเซิร์ฟเวอร์ เปิด localhost ก็จะรันเซิร์ฟเวอร์ปุ๊บ ได้อย่างงี้ ทีนี้เรารอลองดู โค้ดดูแล้วกันมา ปุ๊บเรียกโค้ด VS code เนาะ อุ้ย ผิดอัน

index.ts ปุ๊บ หน้าตาจะเป็นประมาณอย่างงี้เนาะ ก็เรียกมา มีเซิร์ฟเวอร์มีอะไรเรียบร้อย รันปุ๊บมันก็จะได้เซิร์ฟเวอร์มา โค้ดสว่าง

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

การเพิ่ม Swagger UI3:00

ผมเกิดอยากได้ body มา ก็คืออยากให้แบบ

API มันรับ endpoint มา ผมก็จะเขียน body แล้วกำหนด type มา ปุ๊บ สมมุติเอาเป็น object แล้วก็ ส่ง name มา อะไรประมาณอย่างงี้ ปุ๊บ เราก็จะได้ endpoint มาอันนึงเนาะ แต่อันนี้ เรายังไม่มีหน้าตาเนาะ เพราะฉะนั้นเดี๋ยวผมโหลด Swagger มา นิดนึง ก็คือโหลด

elysiajs/swagger ปุ๊บ

แล้วเราก็ยัด Swagger ใส่ use(swagger()) ปุ๊บ โอเค มา ก็จะได้อย่างงี้ รันปุ๊บแล้วก็ไปที่ หน้าบ้านเนาะ ปุ๊บ เรา /swagger ปุ๊บ ก็จะได้หน้า Swagger มาอย่างงี้ ทีนี้เนี่ย มันจะมีเมจิกนิดนึงก็คือ คือมันไม่ใช่ Swagger ด้วยซ้ำ เฮ้ย มันปรับเป็น Swagger ได้ด้วยนะ เนี่ย เรา เราปรับเป็น มันชื่ออะไรวะ มันชื่ออะไรวะ ลืมละ provider ใช่ แล้วก็ swagger-ui ปุ๊บ เซฟ แล้วก็รีโหลด มันก็จะได้มาเป็น

Swagger ได้ Swagger มา

ทีนี้ endpoint เนี่ย สมมุติว่าเรามี endpoint นี้ใช่ไหม ตอนเนี้ย endpoint เนี่ย มันรับ name มา แล้วก็จะส่งค่ากลับมาเนาะ สมมุติในอนาคตเนี่ย เราอยากเพิ่ม field 1 อัน แล้วก็เพิ่ม age มา ปึ๊บ มันก็จะมาโผล่ในนี้เนาะ แต่ทีนี้ปัญหาคือ สมมุติเราทำอย่างงี้ frontend ที่เขียนหน้าบ้านเราเนี่ย

ปัญหาการ Sync Type ระหว่าง Frontend และ Backend4:24

ก็จะไม่รู้ว่า type ตรงนี้มันเปลี่ยน แล้วก็ต้องไป slack ไปบอก frontend ว่า มันมี field ตรงนี้เพิ่มมานะ แล้วคิดภาพสมมุติว่า เราทำการ migration ครั้งใหญ่เงี้ย field มันเปลี่ยนหมดเลย หรือแบบเพิ่ม endpoint แบบ v1 v2 อะไรเงี้ย มันก็จะมีปัญหาใช่ไหม ทีนี้ เราก็เลยแก้ปัญหาด้วยการเพิ่มตัว end-to-end type safety

การใช้งาน End-to-End Type Safety4:43

เมื่อกี้ก็คือเราลง plugin ชื่อ eden เนาะ

แล้วก็โหลดเข้ามา รอแป๊บนึง ทีนี้ อันนี้มันเป็นปกติจะใช้ monorepo เนาะ แต่อันนี้ขอเป็นไฟล์เดียวเลย จะได้เห็นว่ามันทำอะไรได้เนาะ เพราะว่า setup monorepo มันนานนิดนึง แล้วก็ import มา edenTreaty ปึ๊บ import function นี้มา แล้วเราก็สร้าง type ขึ้นมา 1 อัน type App แล้วกัน typeof App ปึ๊บ ทีนี้ ตัวหน้าบ้านเนี่ย คิดสภาพว่าอันนี้เป็นหน้าบ้านแล้วกัน ผมสร้างไฟล์ใหม่แล้วกัน จะได้เห็นเนาะ เอา b.ts ปึ๊บ สร้างหน้าบ้านมา

โอเค ประมาณอย่างงี้ ผมก็ import ตัวเมื่อกี้มา edenTreaty ปึ๊บ

แล้วก็ import type เมื่อกี้มาเนาะ index ปึ๊บ App ปึ๊บ ได้อย่างงี้

ทีนี้ สมมุติไฟล์ซ้ายคือ server ไฟล์ขวาคือหน้าบ้านเนาะ ผมก็สามารถทำอย่างงี้ได้ ก็คือเรียก treaty อันนี้ แล้วผมก็เขียนว่า endpoint เนี่ย อยากให้มันคุยไปที่ไหน ผมก็เขียนเป็น localhost 3000 เนาะ import declaration not found อ๋อ มัน name มันซ้ำกัน เอาเป็น api อะไรประมาณอย่างงี้ ผมก็เรียก function นี้มา ผ่าน type เข้าไปในนี้ แล้วก็เขียน endpoint ทีนี้ ตัว api ของเราก็จะสามารถคุยได้ว่า เหมือนจะมี auto complete ว่าเราอยาก get ที่ index ปึ๊บ แล้วก็มี method ขึ้นใช่ไหม ความเจ๋งก็คือ อันนี้มันก็จะบอกว่า ถ้าสมมุติผมตัด body ออกเนาะ ปึ๊บ ปึ๊บจะเห็นว่าตรงนี้ type มันไม่ error แล้ว เพราะว่าตรงนี้มันบอกไม่ต้องมี body อะไร แต่ถ้าเราใส่ body มา มันก็จะมี type ขึ้นอย่างนี้เนาะ อะไรประมาณอย่างนี้เราก็ใส่ให้ครบปึ๊บ age

ใช่ มันก็จะ auto-complete ให้ แล้วสมมติในอนาคตเรามี field เพิ่มขึ้นมาอีก ก็เอาเป็น อะไรดี boolean

มันจะฟ้องว่า field เนี่ยมันขาดไปอันนึงนะ เพราะฉะนั้นเนี่ยมันก็เลยสามารถ sync type กันได้ใช่มั้ย

อธิบายการทำงานเบื้องหลังของ End-to-End Type Safety6:36

ทีนี้เนี่ยอันนี้มันแอบ magic นิดนึง ก็คือตรงที่ว่า สิ่งที่มันทำจริงๆ แล้วเนี่ย มันไม่มีการ เห็นไหม มันไม่มีการ compile มันไม่มีอะไรเลย เราสามารถ run ได้เลย เดี๋ยวผม run ไฟล์นี้ให้ดูเลยว่าเกิดอะไรขึ้นเนาะ อันนี้ return body อันนี้กลับมาด้วย แล้วเราก็จะใส่ age มาอย่างนี้ ปึ๊บ เอา await data error มา

ทีนี้ถ้าเราลองดู มันจะเห็นว่า data ตรงนี้มันก็จะได้ data กับที่เรา return กลับมาเนาะ ทีนี้ไอ้ตรงนี้มันมี magic ก็คือ ตรงนี้มันไม่ได้มีการ compile ไม่ได้มีการทำอะไรๆ เลย มันแค่ import type มาแล้วเราก็ได้ทุกอย่างออกมา ปัญหาคือตอนเขียน JavaScript จริงๆ แล้วเนี่ย เวลาเรา compile TypeScript เนี่ย เวลาเรา compile TypeScript type มาเป็น JavaScript เนี่ย ตัว type เนี่ยมันจะหายไปก็คือ มันจะละลายหายไปเลย คือ JavaScript เนี่ยมันจะไม่เห็นว่ามันมี type อะไรอยู่เนาะ เพราะฉะนั้นเนี่ย

สิ่งที่เกิดขึ้นจริงๆ ตรงนี้ คือมันเป็นอะไรที่ คุณลองคิดภาพ สมมติผมเปลี่ยนไฟล์นี้เป็น JavaScript เนี่ย มันก็ยังไม่มี auto-complete ไม่มีอะไร type check อะไรเลยใช่มั้ย แต่ว่ามันก็ยังทำงานได้อยู่นะ เพราะว่ามันใช้ สิ่งที่เรียกว่า พูดง่ายๆ คือเราใช้ type เนี่ยมาบอกว่า user ควรเขียนอะไร แต่จริงๆ แล้วเนี่ย ถ้าสมมติผมเขียนผิดเงี้ย มันก็ยัง run อยู่นะ เพียงแค่มัน run ไม่ถูก สมมติผม run ว่า ตรงนี้ ผมเปลี่ยนอันนี้เป็น something else ละกัน เอาเป็น stage อะไรประมาณเงี้ย มันก็จะเห็นว่ามัน error ใช่มั้ย แต่จริงๆ แล้วเนี่ย ถึงแม้มันจะ error มันก็ยัง run ได้อยู่ เพราะว่าจริงๆ แล้วเนี่ย ไอ้ตัวนี้ ตัว type ตัวนี้มันเป็นการเตือนว่า มันควรเขียนอย่างนี้นะ แต่จริงๆ แล้วคุณก็ยังเขียนได้อยู่ ก็คือมันแบบว่า เรา

แต่ว่า ปิ๊บ คือในความเป็นจริงแล้วเนี่ยคุณเห็น type error แล้วคุณก็คงไม่เขียนแล้วล่ะ ผมก็เลยจริงๆ แล้วเนี่ยอันนี้ หลักการการทำงานของอันนี้มันง่ายมากเลย

การใช้ JavaScript Proxy ในการทำ End-to-End Type Safety8:20

ก็คือคิดภาพว่า JavaScript เนาะเราก็จะใช้ API ตัวนึงที่ชื่อว่า proxy proxy เนี่ยเหมือนเป็นการบอกว่าเอ้ย ถ้าตัวนี้มันถูก access แล้วให้เราทำอะไรก็ได้เช่นผมบอกว่า get มันชื่อ get ป่ะผมจำไม่ได้เหมือนกัน เดี๋ยวนะ อ่า object อันนึงก่อน ทีนี้มันก็สามารถบอกว่าเอ้ยสมมุติ API ไอ้ตัวนี้ ทำอะไรก็ตามที่อยู่ในนี้ get ก็คือ สมมุติผมเรียก get เนาะ get เนี่ยก็คือบอกว่าเอ้ยถ้าเราเรียก a.something อะไรประมาณนี้เป็น object ปกติ มันก็จะไป run ฟังก์ชันนี้แล้วให้เรา return อะไรกลับมาก็ได้ เหมือนเป็นการดักว่าเอ้ย ถ้าฟังก์ชันนี้ถูกเรียกอ่ะ ก็ให้เราสามารถแบบเอ้ยอ่าน key อ่าน value

ว่ามันพยายามจะทำอะไรอยู่ สิ่งที่ผมทำจริงๆ เนี่ยด้วยความที่เราไม่รู้ว่า type มันคืออะไรเนาะ มันไม่สามารถอ่าน type ได้ผมก็เลยทำการบอกว่าเอ้ย หลักการของอันนี้ง่ายมากเลยคุณอยากได้ endpoint เท่าไหร่อ่ะ stage/a/b/c อย่างเงี้ย ผมก็บอกว่าเอ้ย ถ้าเรามี api.stage.a.b.c

อันนี้ก็คือสิ่งที่ TypeScript เห็นก็คือสิ่งที่เราเห็นเนาะ เป็น end user เห็นแล้วเราก็ยัด type ลงไป สิ่งที่ code เห็นจริงๆ เนี่ยคือการทำอย่างเงี้ย ทำอย่างเงี้ยซ้ำไปเรื่อยๆ แล้วก็เรียกเอ้ย ถ้า method ตรงนี้มันอาจจะเอาเป็นว่า HTTP เนี่ยมันจะมีวิธีการเรียก method บางอันอยู่ เช่นแบบ get post put อะไรพวกนี้แล้วผมก็แบบแก๊สไลท์ว่าเอ้ย ผมก็หลอก user ว่าด้วยความที่ผมน่ะ เราอยากให้ user อ่ะรู้ว่า ด้วยความที่แบบ เออเราอยากให้มันตรงนี้มันใช้งานได้ผมก็เลยแก๊สไลท์ user ว่า เอ้ยถ้าไอ้ตรงนี้ ตรงนี้เป็นการเรียก method แล้วเป็นการเรียกฟังก์ชัน ผมก็เดาว่าเอ้ยตรงนี้มันก็น่าจะเป็นการเรียก endpoint แล้วมันไม่น่า ปกติสมมุติเป็น a อย่างเงี้ย .a อย่างเงี้ยคุณก็คงแบบว่าเอ้ย มันคงเรียกผ่านแหละมันไม่น่าใช่แบบการเรียกฟังก์ชัน ไม่น่าใช่การเรียก method เพื่อเรียก endpoint หรอกเนาะ ผมก็แก๊สไลท์ว่าเอ้ยถ้าคุณอยาก

เทคนิคการ 'แก๊สไลท์' ผู้ใช้ในการเขียน API10:09

ผมก็ เออ คือการเขียนอันนี้มันเป็นการแก๊สไลท์อย่างนึงแบบขั้นสุดเลยเว้ยก็คือตรงนี้ เราไม่รู้ว่า user เนี่ยจะเรียก endpoint เมื่อไหร่ สิ่งที่ผมทำก็คือบอกว่าแก๊สไลท์ว่าเอ้ย ถ้าคุณอยากเรียก endpoint นะคุณเรียก HTTP method แล้วก็ใส่ body เข้ามา ซึ่งเคสที่มันมีโอกาสเรียกจริงๆ เนี่ยอ่ะสมมุติ สมมุติผมเขียนอย่างงี้อ่ะเป็น state แล้ว get ใช่มั้ย อ่ะอย่างงี้เนาะ ปี ก็สามารถบอกว่า เรียก api.space.get แต่เห็นไหมว่าตรงนี้มันไม่มีการเรียกฟังก์ชัน เพราะว่ามันยังมีผาดด้านหลังอยู่ แต่สมมุติว่าผมเรียก เป็นประมาณอย่างนี้ api.stage.get ด้วยความที่ตรงนี้ มันยังไม่ใช่การเรียก มันยังไม่ใช่การเรียกครั้งสุดท้าย เพราะฉะนั้นเนี่ยผมก็สามารถแก๊สไลท์ได้ว่า ถ้ามันเป็นครั้งสุดท้าย แล้วมันเป็นการเรียก ถ้ามันเป็นครั้งสุดท้ายแล้วไปต่อเนี่ย มันชื่อตรงกับ http verb อันนึง ก็ให้มันไปเรียก เราก็แทนที่จะรีเทิร์นอันนี้ recursive เนาะ แล้วก็ไปเรียก fetch endpoint มา มันก็จะทำการ เรียก endpoint อีกทีนึง เพราะฉะนั้นเนี่ยสิ่งที่ผมทำจริงๆแล้วเนี่ย มันไม่ใช่ว่า เราเอา type แล้วเราก็ get type แล้วก็

แก้ something แต่มันเป็นการแบบว่าบอกว่า เราตรงนี้มาทำงานได้แบบ magic เลย คุณไม่ต้องไปซีเรียสเรื่องแบบ implementation หรอก เดี๋ยวเราจัดการเอง ซึ่ง implementation ด้านหลังจริงๆแล้วเนี่ย

ข้อจำกัดทางวิศวกรรมและวิธีการแก้ปัญหา11:27

มันมี limit ทาง engineering อยู่

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

มัน มี edge case แหละ เราก็เลยบอก user ว่า ถ้าคุณเขียนประมาณอย่างนี้ เราจะสามารถเลี่ยง edge case ที่เราเจอแล้วเรายังไม่สามารถแกะได้ ก็บอกว่า ใน api.doc เนี่ย เราเขียนประมาณอย่างนี้นะ แล้วมันจะเจอโอกาสที่แบบ เลี่ยง edge case ที่แบบว่า ที่เราบอกไปเนี่ย มันให้มันลดลง เพราะฉะนั้นสิ่งที่เราทำจริงๆแล้วเนี่ย

สรุปแนวคิดในการพัฒนา Framework12:16

ในการเขียน framework มันไม่ใช่แค่แบบว่า คุณต้องเขียนแบบวางแพลนโคตรเก่งเลย

แล้วก็แบบว่าเขียน implementation บางทีมันมีแบบ

limit บางอย่างที่แบบเรา แก้ได้ค่อนข้างยาก แต่เราสามารถเลี่ยงได้ด้วยการแบบว่า เราเขียน illusion something ที่แบบ สร้างภาพลวง อันนึงออกมาว่า ถ้าเขียนอย่างนี้ มันจะโอกาสที่เป็นไปได้ มันน่าจะมากกว่านะ อะไรประมาณอย่างนี้ แล้วก็แบบว่า หลอกให้ user เนี่ย เขียนเป็นประมาณที่เราต้องการ แล้วก็มันก็จะได้ ตัว API ออกมาที่แบบ ที่เราต้องการเลย ก็ประมาณนี้ครับ

12 นาทีพอดีด้วย