3 คะแนน โดย GN⁺ 2024-10-23 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Rwf เป็นเฟรมเวิร์กแบบครบวงจรสำหรับสร้างเว็บแอปพลิเคชันด้วย Rust โดยอิงกับแพตเทิร์น MVC แบบคลาสสิก และมีฟีเจอร์ที่จำเป็นสำหรับสร้างเว็บแอปที่รวดเร็วและปลอดภัยมาให้ในตัว
  • ฟีเจอร์พื้นฐานประกอบด้วย HTTP server, ORM สำหรับคิวรี PostgreSQL, dynamic templates, การยืนยันตัวตนและเซสชันผู้ใช้, middleware, background jobs และ scheduled jobs รวมถึง database migrations
  • ยังมี REST framework ในตัวที่รองรับ JSON serialization, WebSockets, static file hosting, การตั้งค่าแยกตาม environment, logging และ metrics รวมถึง CLI
  • ผสานรวมกับ Hotwired Turbo อย่างแนบแน่น ทำให้สร้าง backend-driven SPA ได้ และมี WSGI server สำหรับย้ายจาก Django/Flask รวมถึง Rack server สำหรับย้ายจาก Rails
  • ขณะนี้อยู่ในสถานะ เบต้า และกำลังมองหาผู้ใช้งานกลุ่มแรก โดยระบุว่าฟีเจอร์ส่วนใหญ่อยู่ในสภาพดีและเอกสารอยู่ในระดับใช้งานได้

เว็บเฟรมเวิร์ก MVC สำหรับ Rust

  • Rwf เป็นเฟรมเวิร์กแบบครบวงจรสำหรับสร้างเว็บแอปพลิเคชันด้วย Rust
  • เขียนตามแพตเทิร์น MVC(model-view-controller) แบบคลาสสิก
  • มีเป้าหมายที่จะรวมฟีเจอร์ที่จำเป็นไว้ในตัว เพื่อให้สร้างเว็บแอปที่รวดเร็วและปลอดภัยได้ง่าย
  • เอกสารมีให้ที่ เอกสารทางการ

ฟีเจอร์ที่มีให้

  • เซิร์ฟเวอร์และการจัดการคำขอ
  • ข้อมูลและโครงสร้างแอปพลิเคชัน
    • มี ORM ที่ใช้งานง่ายสำหรับสร้างคิวรี PostgreSQL ได้สะดวก
    • รองรับ database migrations
    • มี Dynamic templates
    • รวม Authentication และเซสชันผู้ใช้ในตัว
  • API และการประมวลผลงาน
  • เครื่องมือด้านปฏิบัติการและการพัฒนา
    • รองรับการตั้งค่าแยกตาม environment
    • มี logging และ metrics
    • รวม CLI

การรองรับ Hotwired Turbo และการย้ายระบบ

  • ผสานรวมกับ Hotwired Turbo อย่างแนบแน่น ทำให้สร้าง backend-driven SPAs ได้
  • มี WSGI server สำหรับย้ายจากแอป Django/Flask
  • มี Rack server สำหรับย้ายจาก Rails

เริ่มต้นอย่างรวดเร็ว

  • สร้างแอปพลิเคชันไบนารี Rust แล้วเพิ่ม rwf เป็น dependency เพื่อใช้งาน
cargo add rwf
  • แอปพื้นฐานใช้ rwf::prelude::* และ rwf::http::Server เพื่อกำหนด controller และ route
use rwf::prelude::*;
use rwf::http::Server;

#[controller]
async fn index() -> Response {
    Response::new().html("Welcome to Rwf!

")
}

#[tokio::main]
async fn main() {
    Server::new(vec![
        route!("/" => index),
    ])
    .launch()
    .await
    .unwrap();
}

ตัวอย่างและสถานะโปรเจกต์

  • ดู use case ทั่วไปได้ที่ examples
  • ขณะนี้ Rwf อยู่ในสถานะ เบต้า และกำลังมองหาผู้ใช้งานกลุ่มแรก
  • ระบุว่าฟีเจอร์ส่วนใหญ่อยู่ในสภาพดีและเอกสารอยู่ในระดับใช้งานได้
  • ยินดีรับการมีส่วนร่วม โดยมีคู่มืออยู่ที่ CONTRIBUTING, คำแนะนำโครงสร้างโค้ดอยู่ที่ ARCHITECTURE และรายการฟีเจอร์ที่ต้องการอยู่ที่ ROADMAP

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

 
GN⁺ 2024-10-23
ความคิดเห็นบน Hacker News
  • การเปิดตัวนี้คงให้ความรู้สึกค่อนข้างเหนือจริง ยินดีด้วย สิ่งใหญ่ที่สุดที่ได้เรียนรู้จากการทำเว็บ MVC ด้วย Rails มานานคือ เฟรมเวิร์กควรควบคุม ตรรกะทางธุรกิจในเลเยอร์โมเดล อย่างเข้มงวด
    ผมคิดว่าไม่ควรใส่ callback แบบที่ Active Record พูดถึงไว้ในโมเดลข้อมูล เช่น hook อย่าง afterCreate ถ้ายังไม่มีใน ORM แต่มีอยู่ในโรดแมป ผมคิดว่าไม่มีจะดีกว่า
    แต่เรื่องนี้จะทำงานได้ดีจริงก็ต่อเมื่อมีการแนะนำอย่างหนักแน่นให้ใช้เลเยอร์บริการ หรือก็คือเลเยอร์ตรรกะทางธุรกิจ แอป Rails ของผมส่วนใหญ่จัดโครงสร้าง command/service object ด้วย gem อย่าง Interactor
    ผมมองว่าแค่ MVC หรือก็คือโครงสร้างพื้นฐานของ Rails เพียงอย่างเดียว ไม่เหมาะนักสำหรับการเขียนแอปที่ใช้งานจริงในโปรดักชัน เพราะ ไม่มีเลเยอร์บริการ และก็สงสัยด้วยว่าทำไมไม่ใช้ ORM หรือ query builder ที่มีอยู่แล้วในคอมมูนิตี้ ผมยังไม่เคยเขียน Rust แม้แต่บรรทัดเดียว แต่ก็ยิ่งเริ่มอยากรู้มากขึ้นเรื่อย ๆ

    • อยากรู้ว่ามุมมองนี้มาจากไหน ความคิดของผมตรงกันข้ามเลย คือ ตรรกะทางธุรกิจควรอยู่ในโมเดล ให้มากที่สุดเท่าที่เป็นไปได้
      ผมมองว่า service แทบทั้งหมดควรเป็นชิ้นส่วนโค้ดเฉพาะที่ซับซ้อนขึ้น ซึ่งถูก trigger จากโมเดล แนวทาง controller บาง โมเดลหนา ให้ผลดีที่สุดในแง่การ debug การจัดระเบียบ และการค้นพบได้ง่าย และจากประสบการณ์ การใช้ service เยอะ ๆ มักทำให้เกิด spaghetti code มาก
      จากมุมมอง object-oriented ล้วน ๆ โมเดลก็คือ object หลักที่นิยาม entity User ควรรู้ทุกอย่างที่จำเป็นต้องรู้เกี่ยวกับตัวเอง และสื่อสารกับ entity อื่นผ่าน message
      แต่เรื่อง callback ผมเห็นด้วย callback สร้าง side effect แปลก ๆ เยอะ และทำให้ debug ยากจริง ๆ
    • ถ้าไม่มีตรรกะทางธุรกิจ แล้ว โมเดล คืออะไรกันแน่ ผมไม่เข้าใจ ฟังดูเหมือนคุณอยากได้โครงสร้างที่เรียก struct ว่าโมเดล และเรียก procedure ว่า service
      ทำแบบนั้นได้ก็จริง แต่จะทำให้การอนุมานว่า entity สามารถถูกอัปเดตด้วยวิธีไหนได้บ้างค่อนข้างยาก เพราะ procedure จำนวนเท่าไรก็ได้สามารถอัปเดตมัน และแต่ละอันก็อาจมีกฎทางธุรกิจของตัวเอง ถ้าจะเป็นแบบนั้น ก็ให้ procedure เขียนลงฐานข้อมูลโดยตรง แล้วตัดโมเดลทิ้งไปก็ได้
    • เห็นด้วยว่าไม่ควรมีตรรกะทางธุรกิจในโมเดล Martin Fowler และ Robert C. Martin เรียกสิ่งนี้ว่า โมเดลโดเมนแบบบาง ซึ่งตรงข้ามกับแนวทางพัฒนา enterprise แบบ object-oriented และเน้น pattern หนัก ๆ ในอดีต
      แต่หลังจากพัฒนามากว่า 20 ปี ผมสรุปได้ว่า encapsulation เป็นภาระ ไม่ใช่ feature และข้อมูลควรถูกแยกออกจากการกระทำที่ทำกับข้อมูลนั้น สิ่งนี้เรียกว่า data-oriented design หรือ data-oriented programming และผมไม่ใช่คนเดียวที่ได้ข้อสรุปแบบนี้
    • การบอกว่าแค่ MVC ไม่พอสำหรับแอประดับโปรดักชันเป็นข้อกล่าวอ้างที่ค่อนข้างใหญ่ ส่วนตัวผมไม่ชอบ service object เพราะมันกระจัดกระจายไปทั่วและทำให้ค้นพบได้ยาก
      ยังมีวิธี modeling แบบอื่นที่ขยายตัวได้ดี และมีบทความเกี่ยวข้องอยู่บ้าง บทความจากคนของ Basecamp คือ https://dev.37signals.com/vanilla-rails-is-plenty/
      แน่นอนว่านี่เป็นแนว object-oriented มาก ๆ ผมเองก็ไม่ได้ชอบนักเช่นกัน ตัวอย่างเช่น เฟรมเวิร์ก Phoenix ของ Elixir ใช้ context เพื่อรวมฟังก์ชันที่เกี่ยวข้องเข้าด้วยกัน พูดง่าย ๆ ก็มองได้ว่าเป็น facade
      ถ้าชอบ service ก็ใช้ได้ แต่ผมคิดว่าการบอกว่า MVC ไม่พอสำหรับระดับโปรดักชันนั้นค่อนข้างคลาดเคลื่อน การจัดการ process ทางธุรกิจหนัก ๆ ด้วย model callback ไม่ใช่เรื่องดี แต่สำหรับงานเล็ก ๆ ที่ปรับข้อมูลให้อยู่ในรูปแบบที่ถูกต้อง ก็ถือว่าใช้ได้ทีเดียว
    • น่าสนใจ ที่บริษัทเราเคยทำ PHP ORM เอง เพราะถูกห้ามใช้ไลบรารีโอเพนซอร์สอย่าง Laravel และผมรู้สึกว่า hook มีประโยชน์มาก
      โดยเฉพาะประสบการณ์เขียนโปรแกรมของผมเริ่มจาก PHP สำหรับ Wordpress ซึ่งใช้ hook อย่างกว้างขวาง เลยอาจมีอคติอยู่บ้าง
      สิ่งที่ผมทำมีสเปกของตารางที่สามารถแปลงเป็นนิยาม SQL หรือปล่อย HTML form ที่จัดรูปแบบสวยงามออกมาได้ มี controller แยกต่างหากที่จัดการการเชื่อมต่อ DB และงาน CRUD ทั้งหมด และมี hook ก่อน/หลังที่สามารถ cross-reference ตารางอื่นกับ constraint ได้ง่ายเมื่อจำเป็น
      มันทำงานได้ค่อนข้างดี แต่ถ้าเป็นไปได้ ผมจะย้ายไป Laravel ทันที
  • ถ้าเป็นเฟรมเวิร์ก REST ผมคาดหวังว่าโดยพื้นฐานแล้วมันควร สร้างนิยาม Swagger/OpenAPI ได้ ผมมองว่านั่นเป็นหนึ่งใน killer feature ของ FastAPI
    อีกอย่าง ผมไม่ค่อยเข้าใจว่าทำไมถึงสร้าง ORM เองแทนที่จะผสานกับ ORM ที่มีอยู่แล้วอย่าง diesel.rs และทำไมถึงสร้างภาษา template เองแทนที่จะเลือก template engine ยอดนิยมที่มีอยู่แล้ว
    นอกเหนือจากนั้น โปรเจกต์ดูน่าสนใจจริง ๆ และผมจะติดตามต่อไป
    [0] https://diesel.rs/
    [1] https://crates.io/categories/template-engine

    • ผมเคยใช้ Diesel เมื่อไม่กี่ปีก่อน และสำหรับผมมันให้ความรู้สึก เป็น Rust มากเกินไป ต้องนิยาม schema สองครั้ง และใช้ struct เดียวกันทั้ง insert กับ query ไม่ได้ เลยแทบจะกลายเป็นต้องนิยามสามครั้ง
      โดยรวมแล้วมันเป็นความพยายามแรกที่ดีในการทำให้ type ของฐานข้อมูลเข้ากับ type ของ Rust แต่ ORM ควรยืดหยุ่นและใช้ง่ายกว่านี้ เพราะมันควรทำให้เขียน query ง่ายขึ้น ไม่ใช่ยากขึ้น :)
      ส่วนฝั่ง template การสร้างภาษาของตัวเองแทบจะเป็นพิธีรับน้องของชมรมเนิร์ดอายุ 30+ ผมไม่ได้อ่าน Dragon Book แต่ตอนเรียนก็อยากลงวิชานั้นมาก ๆ สิ่งเดียวกันมักมี implementation หลายแบบเสมอ และรู้สึกว่าการใช้ implementation นี้ ซึ่งเลียนแบบสิ่งที่ ERB ของ Rails ทำได้ใกล้เคียงมาก เป็นทางเลือกที่ถูกต้อง
    • https://github.com/poem-web/poem เป็นเฟรมเวิร์ก Rust ที่มี นิยาม Swagger มาให้ในตัว
    • อยากรู้ว่ามีใครรู้จักเฟรมเวิร์กคล้าย ๆ กันที่ทำงานกลับกันไหม คือคง นิยาม OpenAPI ไว้เป็นแหล่งความจริง แล้วตรวจสอบว่า server ทำตามนั้นหรือไม่
    • ผมมองว่าการ generate OpenAPI จาก server เป็น killer feature 100% เราใช้ Dropshot ได้ดีมาก
    • ผมเองก็คาดหวังว่าเฟรมเวิร์ก REST จะสร้างนิยาม Swagger/OpenAPI เป็นค่าเริ่มต้น แต่เท่าที่เข้าใจ นี่ไม่ใช่เฟรมเวิร์กสำหรับสร้างเว็บ API แต่เป็น เฟรมเวิร์ก MVC ที่ส่งคืน HTML
  • ในมุมมองของ SRE ผมสนใจ https://levkk.github.io/rwf/migrating-from-python/ ขึ้นมา ด้านหนึ่งก็เจ๋งมากที่ทำสิ่งนี้ได้ แต่อีกด้านจากมุมของ SRE ที่มองเรื่องเสถียรภาพแล้ว ก็ทำให้แอบกรีดร้องอยู่ในใจ
    ถ้าขนาดระบบใหญ่ขึ้น เรื่องนี้ควรให้ reverse proxy อย่าง Caddy, Nginx, Traefik จัดการ

    • ผมก็คิดเหมือนกัน แต่ด้วยวิธีนี้ วิศวกรแอปพลิเคชันสามารถทดสอบการเปลี่ยนแปลงในเครื่องได้โดยไม่ต้องสลับไปมา สุดท้ายก็วนกลับไปที่ข้อถกเถียงเก่าเรื่อง monolith vs microservices
      การเขียนเซิร์ฟเวอร์ WSGI ที่เสถียรนั้นเป็นไปได้ และถ้าใส่ใจรายละเอียดเล็กน้อย เช่น จำนวนเธรด, vacuum, หรือการรีสตาร์ตทุก ๆ n request แบบ php-fpm สมัยก่อน ก็ไม่ได้ยากนัก โดยพื้นฐานแล้ว ถ้า implement ตัวเลือกส่วนใหญ่ที่ uwsgi มี ก็ถือว่าเดินมาถูกทางแล้ว มีอยู่ในโรดแมปที่จะทำให้ Rwf เทียบเคียงกับการรัน Gunicorn ได้
    • ถ้าผู้เขียนมองว่าผู้อ่านเป็นสายเทคนิค ก็คงสันนิษฐานว่าไม่จำเป็นต้องบอกเรื่องความจำเป็นของ reverse proxy หน้า WSGI server ให้ฟัง
  • การเลือกชื่อนี่สุดยอดจริง ๆ ก็เป็นวิธีหนึ่งที่จะขึ้นมาอยู่อันดับบนสุดของผลการค้นหาแหละ :P
    พูดจริง ๆ คือทำออกมาดี การสร้างเฟรมเวิร์กที่ดีเป็นงานใหญ่กว่าที่คิดมาก และการได้เห็นผู้คนสำรวจพื้นที่การออกแบบและลองไอเดียใหม่ ๆ ก็เป็นเรื่องดีเสมอ

    • โปรเจกต์ Rust มักมีแนวโน้มเลือกชื่อที่กว้าง ทั่วไป และโชว์ตัวเองแบบนี้ ซึ่งพูดตรง ๆ ผมว่ามันเป็นความผิดพลาดใหญ่ Crates.io จะกลายเป็นสุสานของโปรเจกต์ที่ถูกทิ้งร้างซึ่งยึดชื่อที่ดูดีไว้ก่อน
    • “Row” ก็เป็นชื่อที่ดี และน่าจะออกเสียงง่ายกว่า rwf
      RustOnWeb.com ก็ซื้อได้ในราคา $10 :) แค่พูดเล่นเฉย ๆ
  • ชอบวลี “ให้นึกถึง Rails ไม่ใช่ Flask” ภาษาที่มี static type ควรมี เฟรมเวิร์กแบบมีจุดยืนชัดเจน อย่าง Rails/Django มากขึ้นหรือดีขึ้นกว่านี้

  • ทำออกมาดี น่าลองผลักดันให้ไปถูกแนะนำใน https://www.arewewebyet.org/ ได้

    • ค่อนข้างแปลกใจที่หน้านี้ไม่มี loco.rs ทั้งที่ดูเหมือนจะใกล้กับ “Rails” ที่สุดในบรรดาเฟรมเวิร์ก Rust
  • ดีเลย! Django/Rails ที่สร้างด้วยภาษาระดับท็อป
    ขอเสนอว่า async-trait น่าจะเสถียรแล้ว ดังนั้นอาจไม่ต้องใช้ macro อีกต่อไป และถ้าเพิ่ม การผสานรวม OpenTelemetry เพื่อให้ได้ metrics และ tracing มาโดยปริยายก็น่าจะดี สำหรับ target Linux ใช้ jemalloc ก็ดีเช่นกัน

    • ขอบคุณครับ ผมลองใช้ async trait มาตรฐานแล้ว แต่ยังไม่รองรับ dynamic dispatch ที่ Rwf ใช้อย่างกว้างขวาง
      ผมวางแผนจะเพิ่มแท็ก OpenTelemetry ให้ฟังก์ชันต่าง ๆ ของ crate tracing ส่วน jemalloc สามารถเพิ่มใน binary app ที่ใช้ Rwf ได้ และไม่จำเป็นต้องใส่เป็น dependency ของไลบรารี
      [1] https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-trait...
    • สงสัยว่าทำไม target Linux ถึงควรใช้ jemalloc หรือทำไม target อื่นถึงไม่จำเป็น
  • ผมเคยพยายามเรียน Rust แต่ยอมแพ้ตั้งแต่ช่วงต้น มาจาก C#, C/C++, Java, Python แล้ว Rust รู้สึกแปลก ๆ
    สิ่งที่ทำให้ผมถอดใจไม่ใช่ความแปลกนั้นเอง แต่เป็นเรื่องที่ ความเร็วในการทำงานหนึ่งงานให้เสร็จ ช้ากว่าเมื่อเทียบกับภาษาอย่าง C#, Java, Python
    ช่วงนี้ผมทำเว็บเป็นหลัก ความใช้งานง่ายและ productivity จึงสำคัญที่สุด Rust อาจดีกว่านิดหน่อยด้าน throughput และความเร็ว แต่ดู benchmark ของเว็บเฟรมเวิร์กยุคใหม่แล้ว C# กับ Java ก็ทำได้ค่อนข้างดีในด้านนี้
    อย่าอ่านว่านี่เป็นการวิจารณ์ Rust หรือด้อยค่าความสำเร็จครั้งใหญ่ของการสร้าง MVC framework ตั้งแต่ศูนย์เลย มันใกล้เคียงกับแนวคิด “เครื่องมือที่เหมาะกับงาน” มากกว่า

    • เข้าใจที่พูดเต็มที่เลย หลายคนรวมถึงผมเองก็มีประสบการณ์คล้ายกันตอนเริ่มต้น
      การจะข้าม learning curve ไปได้ต้องมีแรงจูงใจที่แรงพอ เช่น จำเป็นต้องใช้ภาษานั้น ถูกดึงดูดด้วย type system และข้อดีด้านความปลอดภัย หรือมีเหตุผลเฉพาะของตัวเอง
      ถ้าไม่มีแรงจูงใจแบบนั้นก็ไม่เป็นไรเลย ตอนแรกผมก็ไม่มี และนั่นทำให้ความพยายามเรียนช่วงแรกไปได้ไม่ไกล อย่างที่หลายคนรู้สึก Rust ใช้เวลานานกว่า Java หรือ Python ในการทำงานให้เสร็จ และต้องคิดล่วงหน้ามากกว่า ในบางสถานการณ์มันคุ้มค่า แต่ถ้าไม่ชัดเจนว่าทำไมถึงควรยอมรับ trade-off นั้น ก็ไม่มีเหตุผลแรงพอที่จะไปต่อ
  • ช่วงหลังผมเลิกใช้ webpack JS loader แล้วหันมาใช้ Rust loader ที่เร็วกว่า 50 เท่า ก็เลยเริ่มติดใจ Rust เหมือนกัน Rust ให้ผลด้านประสิทธิภาพที่ชัดเจนมาก และตอนนี้ C++ กับ Rust เป็นภาษาที่ผมชอบที่สุด

    • โอ้ แชร์ได้ไหมว่าเลิกใช้ webpack แล้วไปใช้อะไรแทน?
  • ผมประเมินตัวเลือกที่มักถูกพูดถึงสำหรับงาน backend Rust ทั้งหมด และทำ prototype เล็ก ๆ มาลองแล้ว จนถึงตอนนี้ แม้จะ low-level กว่าที่อยากได้ แต่โดยรวมก็ต้องยอมเห็นด้วยแบบไม่เต็มใจนักกับข้อสรุปของกลุ่มว่า Axum เป็นตัวเลือกที่ดีที่สุด
    ตัวเลือกอื่นคือ Loco กับ Rocket ส่วน Loco คล้าย Rails เกินไป และ Rocket พอใช้ Rust มากขึ้นแล้ว macro ก็เริ่มขัดใจ
    เฟรมเวิร์กนี้ดูเหมือนจะตรงกับเกณฑ์ของผมพอดีว่า “มีแบตเตอรี่ให้พร้อม แต่ไม่บังคับแนวทางมากเกินไป” เป็นส่วนเพิ่มที่ดีต่อ ecosystem

    • แล้ว Actix ล่ะ? เท่าที่จำได้ เคยมีการถกกันใน HN เรื่อง Actix กับ Axum