เริ่มต้น Backend
สิ่งที่จะสร้าง
หัวข้อที่มีชื่อว่า “สิ่งที่จะสร้าง”Cargo workspace ของ Rust จริง ๆ ภายใน taskflow/backend/ โดยมี member crate ตัวเดียวคือ api (ชื่อ package taskflow-api) ที่จะเติบโตกลายเป็น Axum server เต็มรูปแบบในโมดูลถัดไป เมื่อจบบทเรียนนี้ cargo run จะพิมพ์ TaskFlow API ออกทาง terminal — เป็นหลักฐานว่า workspace, crate และทุก dependency คอมไพล์เข้าด้วยกันได้ ก่อนที่เราจะเขียน handler สักตัว
backend/├── Cargo.toml # workspace root└── api/ ├── Cargo.toml # crate taskflow-api └── src/ └── main.rsCargo workspace ให้หลาย crate ใช้ Cargo.lock และไดเรกทอรี build target/ ร่วมกันได้ วันนี้ TaskFlow มี crate เดียว (api) แต่การจัดโครงสร้างเป็น workspace ตั้งแต่แรกหมายความว่าการเพิ่ม crate ตัวที่สองในภายหลัง — เช่น library taskflow-core ที่รวม domain type ที่ใช้ร่วมกัน หรือเครื่องมือ CLI สำหรับ admin — จะเป็นแค่การเพิ่มบรรทัดเดียวใน members โดยไม่ต้องปรับโครงสร้างใหม่และไม่ต้องมี dependency เวอร์ชันซ้ำซ้อน
ข้อดีข้อเสีย
หัวข้อที่มีชื่อว่า “ข้อดีข้อเสีย”ข้อดี
Cargo.lockไฟล์เดียวสำหรับทั้ง backend — ทุก crate ใน workspace resolve dependency ไปที่เวอร์ชันเดียวกันเป๊ะ ๆ หลีกเลี่ยงบั๊กจาก version skew- ไดเรกทอรี
target/ที่ใช้ร่วมกัน ทำให้ crate ที่พึ่งพากันไม่ต้องคอมไพล์ dependency ที่ใช้ร่วมกันซ้ำสองครั้ง - ขยายได้ไม่ยาก: วันนี้มี crate เดียว (
api); พรุ่งนี้อาจเป็นapi+core+ binary สำหรับ background-worker ทั้งหมด build ได้ด้วยcargo buildคำสั่งเดียว
ข้อเสีย
- สำหรับโปรเจกต์ที่มี crate เดียว workspace เพิ่มพิธีกรรมเล็กน้อย (มี
Cargo.tomlเพิ่มอีกไฟล์) เมื่อเทียบกับการcargo newเปล่า ๆ ที่ root ของ repo - path ที่อ้างอิงแบบ workspace-relative (
members = ["api"]) หมายความว่าCargo.tomlของแต่ละ member crate อยู่ลึกกว่าหนึ่งชั้นเมื่อเทียบกับการทำเป็น project เดี่ยว ๆ — ลืมง่ายเวลา copy-paste path
เรายอมรับ overhead เล็กน้อยนี้ตอนนี้ เพราะโมดูล testing ของ TaskFlow ในภายหลังจะเพิ่มการตั้งค่า integration test ที่ได้ประโยชน์จากโครงสร้าง workspace
ลงมือสร้าง
หัวข้อที่มีชื่อว่า “ลงมือสร้าง”1. สร้าง crate ด้วย cargo new
หัวข้อที่มีชื่อว่า “1. สร้าง crate ด้วย cargo new”จากไดเรกทอรี taskflow/backend/:
cd taskflow/backendcargo new apiคำสั่งนี้สร้าง api/Cargo.toml และ api/src/main.rs พร้อมโครง “Hello, world!” มาตรฐานของ Cargo ต่อไปเราจะเปลี่ยน api ให้เป็น workspace member และใส่ dependency จริงเข้าไป
2. Cargo.toml ของ workspace ที่ root
หัวข้อที่มีชื่อว่า “2. Cargo.toml ของ workspace ที่ root”สร้าง taskflow/backend/Cargo.toml (ไฟล์ ใหม่ แยกจาก api/Cargo.toml):
[workspace]members = ["api"]resolver = "2"members ระบุ crate ทุกตัวที่เป็นสมาชิกของ workspace นี้ — ตอนนี้มีแค่ api resolver = "2" คือการเลือกใช้ feature resolver รุ่นใหม่ของ Cargo ที่เป็นค่า default สำหรับ workspace ใหม่ และช่วยหลีกเลี่ยงปัญหาการรวม feature ข้าม crate ที่ไม่คาดคิด
3. api/Cargo.toml — รายการ dependency ครบชุด
หัวข้อที่มีชื่อว่า “3. api/Cargo.toml — รายการ dependency ครบชุด”แทนที่ api/Cargo.toml ที่ถูก generate ด้วยเนื้อหานี้:
[package]name = "taskflow-api"version = "0.1.0"edition = "2021"
[dependencies]axum = { version = "0.7", features = ["ws", "macros"] }tokio = { version = "1", features = ["full"] }tower-http = { version = "0.6", features = ["cors", "trace"] }sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "uuid", "chrono", "macros"] }serde = { version = "1", features = ["derive"] }serde_json = "1"uuid = { version = "1", features = ["v4", "serde"] }chrono = { version = "0.4", features = ["serde"] }jsonwebtoken = "9"argon2 = "0.5"deadpool-redis = "0.16"redis = { version = "0.27", features = ["tokio-comp"] }thiserror = "1"tracing = "0.1"tracing-subscriber = { version = "0.3", features = ["env-filter"] }สังเกตว่า name ของ package คือ taskflow-api แม้ว่าไดเรกทอรีจะชื่อ api — ชื่อ crate กับชื่อไดเรกทอรีไม่จำเป็นต้องตรงกัน และการใส่ prefix taskflow- ให้ชื่อ crate ที่จะ publish ช่วยหลีกเลี่ยงการชนกับ crate api ตัวอื่นบน crates.io
dependency แต่ละตัวมีไว้ทำอะไร:
axum(ws,macros) — web framework ที่จะ route ทุก HTTP request;wsเปิดใช้ WebSocket upgrade ที่โมดูล Realtime จะใช้macrosเปิดใช้ macro อย่าง#[debug_handler]ที่ช่วยด้าน ergonomics ของ handlertokio(full) — async runtime ที่ทุกอย่างรันอยู่บนนั้น;fullดึง TCP listener, task scheduler, timer และ sync primitive ที่เราต้องใช้ตลอดทุกโมดูลเข้ามาด้วยtower-http(cors,trace) — middleware layer:corsให้ browser ที่FRONTEND_ORIGINเรียก API ได้tracelog ทุก request/response ผ่านtracingsqlx(runtime-tokio,postgres,uuid,chrono,macros) — driver PostgreSQL แบบ async ที่ตรวจ query ตอน compile-time;uuidและchronoให้ SQLx map คอลัมน์uuidและ timestamp ของ Postgres ไปเป็น Rust type ได้ตรง ๆmacrosเปิดใช้query!/query_as!serde(derive) —#[derive(Serialize, Deserialize)]สำหรับทุก struct ของ request/response และโมเดลฐานข้อมูลserde_json— encode/decode JSON ที่axum::Jsonใช้ภายใน และตรงไหนก็ตามที่เราสร้าง JSON เองuuid(v4,serde) — สร้าง UUID แบบ v4 แบบสุ่ม ที่ใช้เป็น primary key ของ board, column และ card; ส่วนserdeทำให้ (de)serialize เป็น JSON ได้chrono(serde) — type สำหรับวันที่/เวลา สำหรับคอลัมน์created_at/updated_atพร้อมserdeรองรับ timestamp ใน JSONjsonwebtoken— encode และ verify JWT access/refresh token ที่ออกในโมดูล Authenticationargon2— hash และ verify รหัสผ่านผู้ใช้; ห้ามเก็บหรือเปรียบเทียบรหัสผ่านแบบ plaintext เด็ดขาดdeadpool-redis— connection pool แบบ async สำหรับ Redis เพื่อให้ handler ยืม connection แทนที่จะเปิดใหม่ทุกครั้งที่มี requestredis(tokio-comp) — Redis client แบบ async ที่อยู่เบื้องหลังdeadpool-redis; ส่วนtokio-compทำให้เข้ากันได้กับ Tokio runtime ของเราthiserror— derivestd::error::ErrorและDisplayให้ enum error ของ domain เรา ซึ่งภายหลังเราจะแปลงเป็น HTTP responsetracing— logging/instrumentation แบบมีโครงสร้างและมีระดับ (level) ครอบคลุมทั้ง servertracing-subscriber(env-filter) — ต่อสาย output ของtracing;env-filterให้เราควบคุมความละเอียดของ log ผ่านตัวแปร environmentRUST_LOG
4. main.rs แบบขั้นต่ำ
หัวข้อที่มีชื่อว่า “4. main.rs แบบขั้นต่ำ”แทนที่ api/src/main.rs ด้วยจุดเริ่มต้นแบบขั้นต่ำ — ยังไม่มี server เป็นแค่หลักฐานว่า crate build ได้และ link ทุก dependency ได้:
fn main() { println!("TaskFlow API");}โค้ดนี้ตั้งใจให้ยังไม่ทำอะไรมากไปกว่านี้ โมดูล Backend Foundations (โมดูล 3) จะแทนที่ด้วยการตั้งค่า Axum server จริง — router, state และ axum::serve
ตรวจสอบผล
หัวข้อที่มีชื่อว่า “ตรวจสอบผล”จาก taskflow/backend/ build และรัน crate api:
cargo run -p apiการรันครั้งแรกจะคอมไพล์ทุก dependency ในรายการข้างบน ซึ่งอาจใช้เวลาหนึ่งถึงสองนาที ผลลัพธ์ที่คาดหวังหลัง build เสร็จ:
TaskFlow APIถ้า compile ผ่านและพิมพ์บรรทัดนั้นออกมา แปลว่า workspace ต่อสายถูกต้องและ dependency ทุกเวอร์ชัน resolve ได้ ยืนยันเพิ่มด้วยว่าการเช็กทั้ง workspace ผ่านสะอาด:
cargo check --workspaceควร exit โดยไม่มี error (คำเตือนเรื่อง dependency ที่ยังไม่ได้ใช้เป็นเรื่องปกติในขั้นนี้ — เรายังไม่ได้ใช้ dependency ส่วนใหญ่)
คุณสร้าง Cargo workspace ที่ taskflow/backend/ แล้ว โดยมี member ตัวเดียวคือ crate taskflow-api ใน api/ Cargo.toml ที่ root ประกาศ members = ["api"]; api/Cargo.toml ล็อก dependency ครบชุดที่ TaskFlow ต้องการ — Axum, Tokio, SQLx, Serde, JWT, Argon2, Redis และ tracing — แต่ละตัวมีจุดประสงค์ที่คุณเข้าใจแล้ว main.rs แบบขั้นต่ำพิสูจน์ว่าทุกอย่าง compile และรันได้ด้วย cargo run -p api ต่อไปเราจะสร้างโครง frontend ใน frontend-init