- 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) แบบคลาสสิก
- มีเป้าหมายที่จะรวมฟีเจอร์ที่จำเป็นไว้ในตัว เพื่อให้สร้างเว็บแอปที่รวดเร็วและปลอดภัยได้ง่าย
- เอกสารมีให้ที่ เอกสารทางการ
ฟีเจอร์ที่มีให้
- เซิร์ฟเวอร์และการจัดการคำขอ
- ข้อมูลและโครงสร้างแอปพลิเคชัน
- API และการประมวลผลงาน
- เครื่องมือด้านปฏิบัติการและการพัฒนา
- รองรับการตั้งค่าแยกตาม environment
- มี logging และ metrics
- รวม CLI
การรองรับ Hotwired Turbo และการย้ายระบบ
เริ่มต้นอย่างรวดเร็ว
- สร้างแอปพลิเคชันไบนารี 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 ความคิดเห็น
ความคิดเห็นบน 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 ยากจริง ๆ
ทำแบบนั้นได้ก็จริง แต่จะทำให้การอนุมานว่า entity สามารถถูกอัปเดตด้วยวิธีไหนได้บ้างค่อนข้างยาก เพราะ procedure จำนวนเท่าไรก็ได้สามารถอัปเดตมัน และแต่ละอันก็อาจมีกฎทางธุรกิจของตัวเอง ถ้าจะเป็นแบบนั้น ก็ให้ procedure เขียนลงฐานข้อมูลโดยตรง แล้วตัดโมเดลทิ้งไปก็ได้
แต่หลังจากพัฒนามากว่า 20 ปี ผมสรุปได้ว่า encapsulation เป็นภาระ ไม่ใช่ feature และข้อมูลควรถูกแยกออกจากการกระทำที่ทำกับข้อมูลนั้น สิ่งนี้เรียกว่า data-oriented design หรือ data-oriented programming และผมไม่ใช่คนเดียวที่ได้ข้อสรุปแบบนี้
ยังมีวิธี modeling แบบอื่นที่ขยายตัวได้ดี และมีบทความเกี่ยวข้องอยู่บ้าง บทความจากคนของ Basecamp คือ https://dev.37signals.com/vanilla-rails-is-plenty/
แน่นอนว่านี่เป็นแนว object-oriented มาก ๆ ผมเองก็ไม่ได้ชอบนักเช่นกัน ตัวอย่างเช่น เฟรมเวิร์ก Phoenix ของ Elixir ใช้ context เพื่อรวมฟังก์ชันที่เกี่ยวข้องเข้าด้วยกัน พูดง่าย ๆ ก็มองได้ว่าเป็น facade
ถ้าชอบ service ก็ใช้ได้ แต่ผมคิดว่าการบอกว่า MVC ไม่พอสำหรับระดับโปรดักชันนั้นค่อนข้างคลาดเคลื่อน การจัดการ process ทางธุรกิจหนัก ๆ ด้วย model callback ไม่ใช่เรื่องดี แต่สำหรับงานเล็ก ๆ ที่ปรับข้อมูลให้อยู่ในรูปแบบที่ถูกต้อง ก็ถือว่าใช้ได้ทีเดียว
โดยเฉพาะประสบการณ์เขียนโปรแกรมของผมเริ่มจาก 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
โดยรวมแล้วมันเป็นความพยายามแรกที่ดีในการทำให้ type ของฐานข้อมูลเข้ากับ type ของ Rust แต่ ORM ควรยืดหยุ่นและใช้ง่ายกว่านี้ เพราะมันควรทำให้เขียน query ง่ายขึ้น ไม่ใช่ยากขึ้น :)
ส่วนฝั่ง template การสร้างภาษาของตัวเองแทบจะเป็นพิธีรับน้องของชมรมเนิร์ดอายุ 30+ ผมไม่ได้อ่าน Dragon Book แต่ตอนเรียนก็อยากลงวิชานั้นมาก ๆ สิ่งเดียวกันมักมี implementation หลายแบบเสมอ และรู้สึกว่าการใช้ implementation นี้ ซึ่งเลียนแบบสิ่งที่ ERB ของ Rails ทำได้ใกล้เคียงมาก เป็นทางเลือกที่ถูกต้อง
ในมุมมองของ SRE ผมสนใจ https://levkk.github.io/rwf/migrating-from-python/ ขึ้นมา ด้านหนึ่งก็เจ๋งมากที่ทำสิ่งนี้ได้ แต่อีกด้านจากมุมของ SRE ที่มองเรื่องเสถียรภาพแล้ว ก็ทำให้แอบกรีดร้องอยู่ในใจ
ถ้าขนาดระบบใหญ่ขึ้น เรื่องนี้ควรให้ reverse proxy อย่าง Caddy, Nginx, Traefik จัดการ
การเขียนเซิร์ฟเวอร์ WSGI ที่เสถียรนั้นเป็นไปได้ และถ้าใส่ใจรายละเอียดเล็กน้อย เช่น จำนวนเธรด, vacuum, หรือการรีสตาร์ตทุก ๆ n request แบบ php-fpm สมัยก่อน ก็ไม่ได้ยากนัก โดยพื้นฐานแล้ว ถ้า implement ตัวเลือกส่วนใหญ่ที่ uwsgi มี ก็ถือว่าเดินมาถูกทางแล้ว มีอยู่ในโรดแมปที่จะทำให้ Rwf เทียบเคียงกับการรัน Gunicorn ได้
การเลือกชื่อนี่สุดยอดจริง ๆ ก็เป็นวิธีหนึ่งที่จะขึ้นมาอยู่อันดับบนสุดของผลการค้นหาแหละ :P
พูดจริง ๆ คือทำออกมาดี การสร้างเฟรมเวิร์กที่ดีเป็นงานใหญ่กว่าที่คิดมาก และการได้เห็นผู้คนสำรวจพื้นที่การออกแบบและลองไอเดียใหม่ ๆ ก็เป็นเรื่องดีเสมอ
rwfRustOnWeb.com ก็ซื้อได้ในราคา $10 :) แค่พูดเล่นเฉย ๆ
ชอบวลี “ให้นึกถึง Rails ไม่ใช่ Flask” ภาษาที่มี static type ควรมี เฟรมเวิร์กแบบมีจุดยืนชัดเจน อย่าง Rails/Django มากขึ้นหรือดีขึ้นกว่านี้
ทำออกมาดี น่าลองผลักดันให้ไปถูกแนะนำใน https://www.arewewebyet.org/ ได้
ดีเลย! Django/Rails ที่สร้างด้วยภาษาระดับท็อป
ขอเสนอว่า
async-traitน่าจะเสถียรแล้ว ดังนั้นอาจไม่ต้องใช้ macro อีกต่อไป และถ้าเพิ่ม การผสานรวม OpenTelemetry เพื่อให้ได้ metrics และ tracing มาโดยปริยายก็น่าจะดี สำหรับ target Linux ใช้ jemalloc ก็ดีเช่นกันผมวางแผนจะเพิ่มแท็ก OpenTelemetry ให้ฟังก์ชันต่าง ๆ ของ crate
tracingส่วน jemalloc สามารถเพิ่มใน binary app ที่ใช้ Rwf ได้ และไม่จำเป็นต้องใส่เป็น dependency ของไลบรารี[1] https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-trait...
ผมเคยพยายามเรียน 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 เป็นภาษาที่ผมชอบที่สุด
ผมประเมินตัวเลือกที่มักถูกพูดถึงสำหรับงาน backend Rust ทั้งหมด และทำ prototype เล็ก ๆ มาลองแล้ว จนถึงตอนนี้ แม้จะ low-level กว่าที่อยากได้ แต่โดยรวมก็ต้องยอมเห็นด้วยแบบไม่เต็มใจนักกับข้อสรุปของกลุ่มว่า Axum เป็นตัวเลือกที่ดีที่สุด
ตัวเลือกอื่นคือ Loco กับ Rocket ส่วน Loco คล้าย Rails เกินไป และ Rocket พอใช้ Rust มากขึ้นแล้ว macro ก็เริ่มขัดใจ
เฟรมเวิร์กนี้ดูเหมือนจะตรงกับเกณฑ์ของผมพอดีว่า “มีแบตเตอรี่ให้พร้อม แต่ไม่บังคับแนวทางมากเกินไป” เป็นส่วนเพิ่มที่ดีต่อ ecosystem