1 คะแนน โดย GN⁺ 2023-10-17 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • การปรับปรุง ServeMux ของ net/http ใน Go 1.22 เพิ่มการจับคู่แพตเทิร์นที่ทรงพลังขึ้นให้กับ HTTP multiplexer มาตรฐาน ช่วยลดพื้นที่ที่เคยต้องพึ่งพา router ภายนอกเพราะมีเพียงการจับคู่ path แบบง่าย
  • mux ใหม่สามารถใช้ HTTP method และ wildcard ของ path อย่าง /task/{id}/ ร่วมกันในแพตเทิร์นได้ ทำให้ path เดียวกันเชื่อมไปยัง handler คนละตัวตาม GET, POST, DELETE ได้
  • ค่าที่จับคู่ได้อ่านได้ด้วย req.PathValue("id") และสามารถควบคุม path ที่ตามมาทั้งหมดหรือจุดสิ้นสุด path แบบตรงตัวได้ด้วย {id}... และ {$}
  • ความขัดแย้งของแพตเทิร์นจะแสดงเป็น panic ตอนลงทะเบียน พร้อมบอกว่า path ใดจับคู่ได้ทั้งสองฝั่ง และเหตุใดจึงไม่มีฝั่งใดเฉพาะเจาะจงกว่าอีกฝั่ง
  • เซิร์ฟเวอร์ที่ใช้ mux มาตรฐานอย่างเดียวแล้วเพียงพออาจมีมากขึ้น แต่ router และ framework ภายนอกอย่าง gorilla/mux หรือ Gin ยังคงมีฟีเจอร์และเครื่องมือที่กว้างกว่า

การเปลี่ยนแปลงของ ServeMux ใน Go 1.22

  • Go 1.22 เตรียมเพิ่ม การจับคู่แพตเทิร์นที่ทรงพลังขึ้น ให้กับ http.ServeMux ซึ่งเป็น HTTP serving multiplexer พื้นฐานของ net/http
  • http.ServeMux เดิมเน้นการจับคู่ path พื้นฐาน จึงมีการใช้ไลบรารีภายนอกจำนวนมากสำหรับ routing ที่ซับซ้อนกว่า
  • mux ใหม่ดึงการจับคู่ขั้นสูงเข้ามาใน standard library เพื่อลดช่องว่างด้านฟีเจอร์กับแพ็กเกจภายนอก
  • ตัวอย่างที่ใช้เปรียบเทียบคือแนวทางเดิมของ standard library และตัวอย่าง REST server ที่ใช้ gorilla/mux

วิธีใช้งานพื้นฐานของ mux ใหม่

  • นักพัฒนาที่เคยใช้แพ็กเกจ router ของ Go อย่าง gorilla/mux จะคุ้นเคยกับวิธีใช้งาน mux มาตรฐานใหม่
  • ตัวอย่างสร้าง mux ด้วย http.NewServeMux() แล้วลงทะเบียนแพตเทิร์นที่สมบูรณ์ขึ้นใน HandleFunc
mux.HandleFunc("GET /path/", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "got path\n")
})

mux.HandleFunc("/task/{id}/", func(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    fmt.Fprintf(w, "handling task with id=%v\n", id)
})
  • handler ตัวแรกใส่ method GET ไว้ในแพตเทิร์น จึงทำงานเฉพาะคำขอ GET ที่เริ่มด้วย /path/
  • handler ตัวที่สองจับคู่ path component เดียวด้วย {id} ใน /task/{id}/ แล้วอ่านค่าจาก handler ด้วย r.PathValue("id")
  • เนื่องจากเป็นตัวอย่างในช่วงที่ Go 1.22 ยังไม่ออก release จึงแนะนำให้รันด้วย gotip
  • ผลการทดสอบช่วยยืนยันกฎ routing ได้ทันที
    • คำขอ /what/ ได้ 404 page not found
    • คำขอ GET ไปยัง /path/ ได้ got path
    • คำขอ POST ไปยัง /path/ ได้ Method Not Allowed
    • คำขอ /task/f0cd2e/ ถูกประมวลผลด้วย id=f0cd2e

ฟีเจอร์แพตเทิร์นเพิ่มเติมและการจัดการความขัดแย้ง

  • ServeMux ใหม่ควบคุมการจับคู่ path ได้ละเอียดขึ้น นอกเหนือจาก wildcard พื้นฐาน
    • ใช้ {id}... เพื่อจับคู่ path ที่ตามมาทั้งหมด เข้า wildcard
    • ใช้ {$} เพื่อจับคู่ จุดสิ้นสุดของ path อย่างเคร่งครัด
    • จัดการลำดับความสำคัญและกฎความขัดแย้งระหว่างแพตเทิร์น
  • ความเป็นไปได้ที่จะเกิดความขัดแย้งเป็นส่วนที่ proposal ให้ความสำคัญเป็นพิเศษ
  • ตัวอย่างเช่น แพตเทิร์นสองรายการต่อไปนี้จะจับคู่คำขอ /task/0/status/ ได้ทั้งคู่
mux.HandleFunc("/task/{id}/status/", ...)
mux.HandleFunc("/task/0/{action}/", ...)
  • ในกรณีนี้ ServeMux ใหม่จะทำให้เกิด panic ตอนลงทะเบียน
  • ข้อความ error จะแสดงทั้งตัวอย่าง path ที่แพตเทิร์นทั้งสองจับคู่ได้ และเหตุผลว่าทำไมไม่มีแพตเทิร์นใดเฉพาะเจาะจงกว่าอีกแพตเทิร์น
  • ในโค้ดที่ซับซ้อนซึ่งลงทะเบียนแพตเทิร์นจากหลายตำแหน่ง การวินิจฉัยความขัดแย้งอย่างละเอียด แบบนี้มีประโยชน์เป็นพิเศษ

การทำตัวอย่าง task server ใหม่

  • นำ task/todo-list server จากซีรีส์ REST Servers in Go เดิมมาทำใหม่ด้วย mux ที่ปรับปรุงใน Go 1.22
  • โค้ดทั้งหมดอยู่ที่ ตัวอย่าง stdlib-newmux
  • แพตเทิร์นที่ลงทะเบียนใช้ HTTP method ร่วมกับ wildcard ของ path
mux.HandleFunc("POST /task/", server.createTaskHandler)
mux.HandleFunc("GET /task/", server.getAllTasksHandler)
mux.HandleFunc("DELETE /task/", server.deleteAllTasksHandler)
mux.HandleFunc("GET /task/{id}/", server.getTaskHandler)
mux.HandleFunc("DELETE /task/{id}/", server.deleteTaskHandler)
mux.HandleFunc("GET /tag/{tag}/", server.tagHandler)
mux.HandleFunc("GET /due/{year}/{month}/{day}/", server.dueHandler)
  • เช่นเดียวกับตัวอย่าง gorilla/mux แม้เป็น path เดียวกันก็สามารถแยกเป็น handler ตาม HTTP method ได้
  • ใน http.ServeMux รุ่นก่อน หลาย method ของ path เดียวกันจะเข้า handler เดียวกัน และต้องตรวจ method ภายใน handler
  • ใน mux ใหม่ การตัดสินใจ routing ถูกจัดการนอก handler มากขึ้น ทำให้แยก handler ได้ง่ายขึ้น

การใช้ PathValue และข้อจำกัด

  • getTaskHandler อ่าน ID ที่ดึงจาก path ด้วย req.PathValue("id") แล้วแปลงเป็นจำนวนเต็มด้วย strconv.Atoi
  • แพตเทิร์น {id} ของ mux ใหม่ไม่ระบุ regular expression ที่จับคู่เฉพาะจำนวนเต็ม จึงต้องจัดการ error จาก strconv.Atoi
  • หากแปลง ID ไม่สำเร็จจะตอบด้วย http.StatusBadRequest และหากไม่พบ task ใน storage จะตอบด้วย http.StatusNotFound
  • implementation สุดท้ายคล้ายกับแนวทางใน Part 2 ที่ใช้ gorilla/mux มาก
  • หากจำเป็น สามารถใช้ wildcard {$} ของ http.ServeMux ใหม่เพื่อจำกัด path ที่ตามมาได้อย่างง่ายดาย

ตำแหน่งของ mux มาตรฐานและ router ภายนอก

  • สำหรับผู้เริ่มต้น Go คำถามว่า “ควรใช้แพ็กเกจ router ใด” เป็นคำถามที่พบบ่อย
  • หลัง Go 1.22 ผู้ใช้จำนวนมากอาจรู้สึกว่า mux มาตรฐานใหม่เพียงอย่างเดียวก็เพียงพอ โดยไม่ต้องใช้แพ็กเกจภายนอก
  • router อย่าง gorilla/mux ยังคงมีฟีเจอร์มากกว่า standard library
  • framework น้ำหนักเบาอย่าง Gin มีไม่เพียง router แต่ยังมีเครื่องมือเพิ่มเติมสำหรับสร้าง backend เว็บด้วย
  • การที่ standard library มีความสามารถมากขึ้นเป็นการเปลี่ยนแปลงเชิงบวกทั้งสำหรับผู้ใช้แพ็กเกจภายนอกและผู้ใช้ที่ใช้เฉพาะ standard library

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

 
GN⁺ 2023-10-17
ความคิดเห็นจาก Hacker News
  • การทำให้เกิด panic เมื่อสอง route match พร้อมกันดูจะขัดกับสัญชาตญาณ เฟรมเวิร์กเว็บอื่น ๆ มักใช้ route ที่ match ซึ่งถูกลงทะเบียนก่อน เลยสงสัยว่ามีเหตุผลเฉพาะของ Go หรือไม่
    กรณีขอบที่ว่า “สามารถลงทะเบียน HTTP route ได้จากหลายที่ จึงหาการ match ซ้ำได้ยาก” ดูเหมือนแก้ได้ด้วยเครื่องมือ ตลอดอาชีพที่ผ่านมา ผมใช้พฤติกรรมแบบ “อันที่ match ก่อนชนะ” อยู่บ่อย ๆ และก็มีความต้องการทางงานไม่น้อยที่ต้องแยก /foo/bar เป็น route หนึ่ง และ /foo/{id} เป็นอีก route หนึ่ง

    • เนื่องจากรับประกันไม่ได้ว่าลำดับการลงทะเบียนจะเหมือนเดิมเสมอ จึงกลายเป็น พฤติกรรมที่ไม่ได้กำหนดไว้ โดยพฤตินัย เดิมที ServeMux ถูกออกแบบและ implement มาแบบนั้น และมองว่าการรองรับพฤติกรรมนั้นต่อไปมีประโยชน์
      ในข้อเสนอการออกแบบ[1] ระบุว่า หาก match ด้วย pattern ที่มีความเฉพาะเจาะจงสูง ก็จะรักษาคุณสมบัติไม่ขึ้นกับลำดับของ pattern ใน ServeMux เดิมไว้ได้ แต่ในบางกรณีอาจดูได้ไม่ชัดในทันทีว่า pattern ใดในสอง pattern มีความเฉพาะเจาะจงมากกว่า หรือทำไมจึงชนกัน ดังนั้นข้อความ panic ที่เกิดเมื่อมีการลงทะเบียน pattern ที่ขัดแย้งกันจึงถูกทำให้แสดงการชนกันด้วยตัวอย่าง path
      ในการอภิปรายเบื้องหลัง[2] ก็อธิบายว่า semantics ของ mux ไม่ขึ้นกับลำดับการเรียก Handle หรือ HandleFunc ความเป็นอิสระจากลำดับทำให้ลำดับการ initialize package ไม่สำคัญ และทำให้ refactor โค้ดง่ายขึ้น ดังนั้นจึงไม่ใช้ลำดับการลงทะเบียนเป็นเกณฑ์ตัดสินกรณีเสมอกัน การลงทะเบียนซ้ำจะทำให้เกิด panic และการหลีกเลี่ยง semantics ที่พึ่งพาลำดับการลงทะเบียนยังคงเป็นเป้าหมายหลักของการออกแบบ
      [1]: https://github.com/golang/go/issues/61410
      [2]: https://github.com/golang/go/discussions/60227
    • เหตุผลที่ใช้ ภาษาที่ compile และมี type อย่าง Go ก็เพื่อดึงบั๊กให้ไปอยู่ทางซ้ายมากขึ้น บั๊กที่ถูกจับได้ตอนเริ่มแอปพลิเคชันย่อมดีกว่าบั๊กที่ไปเกิดใน production จากกรณีมุมอับ และบั๊กที่ถูกจับได้ตอน compile ก็ยิ่งดีกว่าบั๊กที่ถูกจับได้ตอนเริ่มทำงาน
      เมื่อไม่นานมานี้ในโปรเจกต์ส่วนตัว ผมใช้ gorilla/mux แล้วเจอบั๊กที่เผลอสร้าง route ทับซ้อนกัน หาก router เกิด panic แทนที่จะรันต่อ ก็คงบังคับให้ผม refactor URL และหลีกเลี่ยงบั๊กนั้นได้หมด
    • panic เองก็คือเครื่องมือ มันทำให้ใช้โค้ดที่คลุมเครือและ reasoning ยากไม่ได้ และทำให้ไม่ต้องพึ่งพาปัจจัยที่อาจเปลี่ยนได้ตามอำเภอใจ เช่นลำดับการ initialize ตัวอย่างเช่น แค่เปลี่ยนชื่อไฟล์ก็อาจกระทบลำดับฟังก์ชัน init ระดับไฟล์ของ Go ได้
      หากต้องการพฤติกรรมแบบ /foo/bar กับ /foo/{id} ก็สร้าง handler ที่รับ path พื้นฐาน แล้วแยก branch ไปยังฟังก์ชัน handler อื่นอย่างชัดเจนภายในนั้นได้ หากต้องการพฤติกรรมที่ไม่มีใน standard library ก็มีไลบรารีอื่นที่มีฟีเจอร์มากกว่าให้เลือกมากมาย
      standard library และภาษาของ Go มักเอนเอียงไปทางระมัดระวังเสมอเมื่อต้องเผชิญความกำกวมที่เป็นไปได้ และด้วยเหตุนี้จึงสร้างสัญชาตญาณที่หนักแน่นและแม่นยำได้ง่ายว่า standard library จะทำงานอย่างไร การอ่านและทำความเข้าใจได้อย่างรวดเร็วและถูกต้องมีค่ามากกว่าการประหยัดเวลาไม่กี่วินาทีตอนเขียนโค้ดมาก
    • /foo/bar กับ /foo/{id} ดูเหมือนน่าจะอนุญาตได้ เพราะตัวแรกเฉพาะเจาะจงกว่า จึงมีลำดับความสำคัญมากกว่า น่าจะไม่เป็นไร
      แต่ถ้ามี /foo/{id}/delete กับ /foo/bar/{action} แล้ว /foo/bar/delete จะ match กับทั้งคู่ และไม่มีฝ่ายใดเฉพาะเจาะจงกว่ากัน ก็น่าจะเกิด panic ฟังดูสมเหตุสมผล แต่ priority แบบให้ฝั่งที่ลงทะเบียนก่อนชนะก็อาจดีกว่า
    • การได้รับ error ดีกว่ามากสำหรับการ debug เมื่อเทียบกับการที่ route ที่ถูก execute จริงเป็นคนละ route แล้วได้ข้อมูลผิด ๆ เคยเจอเรื่องแบบนี้จริงใน Django เพราะ route ขาด regex $ ท้ายสุด
  • โปรเจกต์ gorilla/mux ค่อนข้างแปลกและชวนสับสน ปีที่แล้ว maintainer archive โปรเจกต์ ผมเลยย้ายไปใช้ multiplexer อีกตัวชื่อ gin-gonic
    แต่พอเห็นถูกพูดถึงในบทความจึงกลับไปตรวจดูอีกครั้ง พบว่าไม่อยู่ในสถานะ archive แล้ว ไม่แน่ใจว่านี่ทำให้ตั้งคำถามต่อเสถียรภาพของทั้งโปรเจกต์หรือไม่ หรือกลับกันคือเป็นหลักฐานว่าโอเพนซอร์สมีเสถียรภาพจริง เพราะแม้ developer จะออกไป คนอื่นก็ยังเข้ามารับช่วงดูแลต่อได้ ถึงอย่างนั้นก็ดีที่ฟีเจอร์นี้มีให้ใน Go เอง

  • ก่อนหน้านี้ก็เคยเขียนไว้ในคอมเมนต์[1] แล้ว แต่ผมมองว่า ไวยากรณ์ของข้อเสนอนี้ผิด
    ต้องสร้างสตริงเวทมนตร์แปลก ๆ เพื่อกำหนด handler ไม่เข้าใจว่าทำไมไม่ทำให้เป็นอาร์กิวเมนต์จริง ๆ ไปเลย แบบนั้นจะใช้ค่าคงที่เดิมได้ง่ายกว่าด้วย
    [1] https://github.com/golang/go/issues/61410#issuecomment-16580...

    • ผมว่าเป็นเพราะ การรับประกันความเข้ากันได้ย้อนหลัง ของ Go มากกว่า
      ถ้าเพิ่มอาร์กิวเมนต์ใหม่ให้ interface ของ Mux โค้ดเดิมจะพัง และในเมื่อเปลี่ยน method signature ไม่ได้ สตริงเวทมนตร์แบบนี้ก็ดูเป็นทางประนีประนอมที่สมเหตุสมผล เมธอด HTTP ก็คงไม่ได้จะเปลี่ยนเร็ว ๆ นี้ และตรวจสอบตอนลงทะเบียนหรือด้วย static analysis ก็ง่าย ไม่ค่อยเห็นว่าการใช้ค่าคงที่จะให้คุณค่าอะไร โดยเฉพาะถ้าต้องทำลายความเข้ากันได้ย้อนหลัง หรือถ้าต้องคง mux API ใหม่ที่ต่างจาก API เดิมแค่นิดเดียวไปพร้อมกันตลอดกาล
    • เพราะมีการ parse {parameter} อยู่แล้ว เลยคิดว่าเป็น สตริงเวทมนตร์ อยู่แล้วไม่ใช่หรือ
      ถ้ามองตามศัพท์ของ RFC2616 ว่าเป็น Request-URI ที่มีเวทมนตร์ของพารามิเตอร์เพิ่มมานิดหน่อย แล้วตรงนี้กลายเป็น [ Method SP ] Request-URI ที่ดูเหมือน request line ก็พอเข้าใจได้ เข้ากันได้ย้อนหลังเต็มที่ และแทบจะชัดเจนว่าทำอะไรโดยไม่ต้องดูเอกสาร
      Method เป็นหนึ่งในค่าคงที่ที่กำหนดไว้ล่วงหน้าหรือเป็น extension-method = token และ token ไม่รวมช่องว่าง สแลช และวงเล็บหลายชนิด ดังนั้นแม้จะเป็น custom method ที่แปลกมาก ๆ ก็ดูไม่น่าจะสับสนหรือถูก parse ผิด
    • เห็นด้วยว่ามันแปลกนิดหน่อย แต่เหตุผลชัดเจน คือไม่อยากทำลายหรือเปลี่ยน interface สาธารณะที่มีอยู่
    • ตอนแรกก็รู้สึกเหมือนกัน แต่พูดตรง ๆ เวลาใช้ Gin เมธอดอย่าง .Get(), .Post() ก็ไม่เคยช่วยแก้อะไรให้เป็นพิเศษ การมีเมธอดลงทะเบียนเดียวที่ใส่ method ไว้ในสตริงก็น่าจะโอเคโดยสมบูรณ์
    • ไม่เข้าใจว่าการลงทะเบียน route HEAD อัตโนมัติมีปัญหาอะไร
  • ไม่ชอบวิธีนี้เลย สงสัยว่ามีเหตุผลอะไรถึงใช้ คำนำหน้า method ที่ทำเป็นสตริง
    type safety ของเมธอดแยกตาม verb อย่าง mux.Get, mux.Post ดีกว่าสตริงเวทมนตร์ที่ตรวจสอบตอน runtime แถมยังใช้ autocomplete หรือ IntelliSense ใน editor ได้ด้วย

    • ของระดับนี้เพิ่มเองได้ง่ายมาก ถ้าใส่ใจขนาดนั้นจริง ๆ ก็ทำแบบ func Get(mux, uri, handler) { mux.HandleFunc("GET " + uri, handler) } ได้เลย
      ที่ละ type ไว้เพราะอยากให้ดูเรียบง่าย
    • ผมก็ไม่ค่อยชอบเหมือนกัน อยากมั่นใจว่า routing ทำงานตอน compile time ไม่ใช่ runtime
    • ถ้าอยากได้ type safety ก็เลือกภาษาที่ type-safe สิ
    • ไม่ได้ชอบการใช้สตริงนัก แต่ถ้าพูดอย่างเป็นธรรม ตอน request เข้ามา HTTP method ก็เป็นสตริงอยู่ดี มีความสวยงามตรงที่มันตรงกับ prefix ของบรรทัดแรกในแพ็กเก็ต HTTP
    • เรื่องนี้ถูกพูดถึงอย่างกว้างขวางในข้อเสนอแล้ว
  • น่าสนใจ ตอนนี้ mux match ได้ถึงระดับ method แล้ว เลยสงสัยว่าถ้า route ตรงแต่ method ไม่ตรงจะเกิดอะไรขึ้น เป็น 404 หรือ 405
    ลองดูแล้วพบว่าส่งกลับเป็น 405 พร้อม header Allow ที่เติมไว้อย่างถูกต้อง
    https://cs.opensource.google/go/go/+/master:src/net/http/ser...
    รู้ว่าหลายคนไม่ชอบ interface แบบสตริง แต่ผมว่าความเป็นไปได้ที่จะพิมพ์ชื่อ HTTP method ผิดนั้นน้อยกว่าความเป็นไปได้ที่จะกรอกอะไรแปลก ๆ แต่ถูกไวยากรณ์เสียอีก ส่วนตัวคิดว่าแค่ static analysis เตือน syntax ที่ผิดได้ก็พอแล้ว
    อย่างไรก็ตาม ถ้ามีความต้องการขั้นสูงอยู่แล้ว ผมว่าไม่ควรใช้ serve mux พื้นฐาน มีตัวเลือกอีกมากที่เหมาะกับ use case หลากหลายกว่า และถ้าสร้าง route แบบ dynamic อยู่แล้ว การเขียน router เองอาจยุ่งยากน้อยกว่าการฝืนยัดโครงสร้างข้อมูลที่มีให้เข้ากับ router เดิม

  • เส้นทางที่ทับซ้อนกันควร match ตามลำดับที่กำหนดไว้ มากกว่าจะ panic
    ในตัวอย่าง ถ้ากำหนดเส้นทาง /task/0/{action}/ ก่อนเส้นทาง wildcard ก็คงคาดหวังว่ามันจะ match ก่อน แบบนี้ถึงจะกำหนด handler สำหรับกรณีพิเศษได้ง่าย
    ผมว่าปล่อยให้มันทำงานตามที่ผมบอกไปเลยดีกว่าการล้มเหลวแบบ “ช่วยเหลือเกินไป” กลิ่นมันแปลก ๆ และไม่ค่อยเป็นสไตล์ Go

    • semantics ของภาษาสำคัญ และนี่จริง ๆ แล้วเป็นตัวเลือกที่ค่อนข้าง สมกับ Go
      ServeMux เดิมถูกออกแบบมาไม่ให้เคารพลำดับการลงทะเบียน เพราะการพึ่งพาลำดับการลงทะเบียนนั้นอันตราย หนึ่งในเป้าหมายหลักของการออกแบบ Go คือการสนับสนุน “การเขียนโปรแกรมในระดับใหญ่” และ side effect แปลก ๆ ที่เกิดจากการแก้โค้ดที่อยู่ไกลกันก็เป็นสิ่งที่ต้องการหลีกเลี่ยงอย่างตรงจุด
      ถ้าเป็นตัวอย่างง่าย ๆ ที่การลงทะเบียนทั้งหมดเกิดในฟังก์ชันเดียวของแพ็กเกจเดียว เจตนาของลำดับการลงทะเบียนก็ชัดเจน แต่ใน codebase ที่ใหญ่พอ การลงทะเบียนมีแนวโน้มเกิดหลายที่ และอาจเป็นผลจากไฟล์ input หรือโค้ดที่ generate มา คนที่เขียนสเปกสำหรับ generated code อาจไม่รู้ความละเอียดอ่อนของลำดับการรันและผลต่อ routing ก็ได้ แค่การเรียง import ของแพ็กเกจตามพจนานุกรมเปลี่ยนไป routing ก็อาจเปลี่ยนโดยไม่คาดคิด
      การหลีกเลี่ยงผลลัพธ์ที่ไม่คาดคิด ทำให้ refactor ได้ง่าย และทำให้ให้เหตุผลกับโปรแกรมที่ซับซ้อนได้ง่ายขึ้น เป็นทิศทางที่สมกับ Go อย่างชัดเจน ไม่จำเป็นต้องเห็นด้วยกับทุกตัวเลือก แต่เหตุผลนั้นสอดคล้องกัน
  • ดีที่ลด dependency ภายนอกไปได้หนึ่งตัว นอกเรื่องนิดหนึ่ง ชุด sqlc + Postgres + templ + htmx + Tailwind เป็น stack ที่ productivity ในการพัฒนาสูงมาก templ ให้ความรู้สึกเหมือน JSX สำหรับ Go

    • จนถึงตอนนี้ประสบการณ์ใช้ sqlc ค่อนข้างดี
      คงต้องลองดู htmx ด้วย
    • สงสัยว่าตรงนี้จัดการ Tailwind กันอย่างไร ไอเดียเพิ่ม Tailwind นั้นดีจริง ๆ แต่ถ้าต้องใช้ npm ผมขอเขียน CSS เองดีกว่า
    • ตอนแรกนึกว่าพิมพ์ template ผิด แต่จริง ๆ แล้วเป็นแพ็กเกจชื่อ templ ที่ต่างจาก standard library แว้บไปดูเอกสารแล้วดูค่อนข้างเรียบร้อย
      เคยหวังว่า bud ที่ผูกการ compile ของ Svelte จะมาเติมช่องว่างนี้ แต่ templ ดูเป็นทางเลือกที่ดีกว่า
    • ถ้า templ มีการรองรับ IntelliJ คงดีมาก ไอเดียนี้ชอบเลย
  • ไม่ชอบข้อเสนอนี้เลยจริง ๆ เอา HTTP request method ไปใส่ไว้ใน URI แถมทำแบบนั้นแค่บางครั้ง และอาจถึงขั้นให้เขียนแบบ POST,PUT,PATCH /something ด้วย ขอบายดีกว่า
    ถ้าจำเป็นต้องทำจริง ๆ ก็ควรทำเมธอดเฉพาะสำหรับรับชื่อ HTTP request method ไปเลย

    • ไม่รู้เหมือนกันว่าทำไม จริง ๆ แล้วมันก็ไม่ได้ต่างกันมาก เป็นแค่ประเด็นเรื่องสไตล์อย่างเดียว คงไม่ถึงกับเกลียดข้อเสนอทั้งหมดได้หรอก
      อยากรู้ว่าเมธอดเฉพาะที่รับชื่อ HTTP method แก้ปัญหาอะไรเมื่อเทียบกับการใส่ method ไว้ในสตริง
    • เห็นด้วย ออกแบบแบบนั้นไม่ค่อยสมเหตุสมผลเท่าไร
      น่าจะมีอาร์กิวเมนต์แยกสำหรับรายการ method หรือ options ได้
  • Go ไม่ใช่เครื่องมือหลักของผม แต่เคยใช้ Gin และมันเป็นแบบนี้:
    router.GET("/", func(context *gin.Context) { ... })
    ดูไม่ได้ต่างจากภาษาและเฟรมเวิร์กอื่น ๆ หลายตัวเท่าไร อยากรู้ว่ามีตัวอย่างอื่นที่ใช้รูปแบบ GET /path/ อีกไหม

    • นอกจากเรื่องความเข้ากันได้ย้อนหลังแล้ว ก็น่าจะไม่มีเหตุผลให้ทำแบบนี้ ดังนั้นคงแทบไม่มี
    • จริง ๆ ไม่เคยเห็นนะ เป็นวิธีที่แย่เฉย ๆ
  • การที่ทีมภาษาทางการใช้ สตริงแบบมีคำนำหน้าแทน enum ดูเป็นการตัดสินใจออกแบบที่ประหลาดมาก
    เช่น (Http.GET, "/path") ดูดีกว่า ("GET /path") ในปัจจุบัน แปลกดี

    • เป็นวิธีรักษา คำมั่นเรื่องความเข้ากันได้ของ Go 1
    • ในฐานะนักพัฒนา Go กรณีแบบนี้อยากให้มี function overloading เพื่อความเข้ากันได้ย้อนหลัง คำมั่นเรื่องความเข้ากันได้นั้นยอดเยี่ยม แต่ความประหลาดที่เกิดจากมันนี่ไม่ค่อยดี
    • เป็นความผิดพลาดที่แก้ไม่ได้โดยไม่ทำให้โค้ดเดิมพังหรือไม่เพิ่มฟังก์ชันใหม่
      ถึงอย่างนั้นก็ยังชอบให้มีหลายฟังก์ชันอย่าง mux.GET, mux.POST มากกว่าวิธีนั้น