สามารถดู video ของหัวข้อนี้ก่อนได้ ดู video
ORM คืออะไร ?
ORM ย่อมาจาก Object-Relational Mapping เป็น technique อย่างหนึ่งในการเปลี่ยนการสื่อสารระหว่าง Relational database จากแต่เดิมที่อยู่ในรูปแบบของ SQL Query มาเป็น Object แทน โดยการมองตารางออกมาเป็น object ที่สามารถ insert, update, delete และ get data ออกมาได้ (เป็นตัวกลางในการแปลงเป็น SQL ออกมาแทน)
ภาพจาก: https://medium.com/@emccul13/object-relational-mapping-9d84807f5536
ซึ่งข้อดีของพวก ORM คือ
- Abstraction ทุกคนในทีมสามารถทำงานผ่าน Object แทนได้ (แทนที่จะต้องมาปวดหัวกับ SQL แทน) ซึ่งจะช่วยทำให้ code อ่านง่ายขึ้นมาก
- Database Agnostic สามารถเปลี่ยนไปมาระหว่าง SQL Database ในหลายๆประเภทได้ (ตามที่ ORM support) โดยการปรับเพียงแค่ config เล็กน้อย
- Maintainability ง่ายต่อการอ่าน code มากขึ้น ทำให้ maintain และเข้าใจได้ง่ายขึ้น
- Security ORM ทุกตัวส่วนใหญ่จะทำการเพิ่มการป้องกันผ่านการโจมตี SQL injection ไว้แล้ว ทำให้เราไม่ต้องกังวลกับเรื่องนี้
จุดพิจารณา
- Performance ที่ถ้าเกิดเขียนไม่ดี อาจจะเสีย query ที่เปลืองกว่าการไม่ใช้ ORM ได้ (โดยเฉพาะพวก JOIN และ GROUP query)
- Learning Curve ต้องมาเรียนรู้ ORM Tools เพิ่ม (จากแต่เดิมที่รู้แค่ SQL ก็ทำได้เลย)
ซึ่งในหัวข้อนี้ เราจะมาลองใช้ Sequelize Node.js ORM ที่ support พวก relational database หลากหลายตัวมากอย่าง PostgreSQL, MySQL, SQLite, MSSQL และสามารถใช้ได้ทั้ง Javascript และ Typescript ด้วย
เราจะมาลองกันผ่าน Session นี้กัน
setting project
- สำหรับ project นี้เราจะ setup docker 2 ตัวคือ mysql และ phpmyadmin ไว้ก่อน (เดี๋ยวเราจะมีเพิ่มบางอย่างตามมาทีหลัง)
docker-compose.yml
Structure project จะเป็นตามนี้
ที่ package.json จะทำการลง package mysql2
และ sequelize
เอาไว้เพื่อให้เห็นภาพว่า หากใช้ท่า ORM และ SQL query มีความแตกต่างกันยังไงบ้าง
สุดท้าย ที่ index.js เดี๋ยวเราจะกลับมาเพิ่มกันหลังเราทำความเข้าใจโจทย์กันแล้ว
โจทย์ของหัวข้อนี้
ขอ reference กลับไปยัง หัวข้อที่ 10 ใน web development 101 link นี้
- ในหัวข้อนี้เราจะมีการใช้ SQL command ทั้งหมด ตั้งแต่ CREATE, VIEW, UPDATE, DELETE
เราจะมาลองปรับกันโดย
- เราจะปรับทุก API มาใช้ ORM แทน
และเราจะลองเพิ่มการ relation database เข้ามาโดยเพิ่มตาราง Address
สำหรับเก็บ address ของ users คู่กันไว้ผ่าน userId แทน (เป็น Foreign Key)
config เริ่มต้น ที่ index.js โดยเราจะ import package มาทั้ง 2 ตัวเลยคือ
mysql2
ท่าที่เราใช้ SQL querysequelize
ท่าที่ใช้ ORM (เป็นตัวแทนของ SQL query)
เริ่มต้น set schema ก่อน
จาก Database design ด้านบน เราจะทำการสร้าง table ผ่าน sequealize (เพื่อให้ sequealize เก็บ model เป็น version ไว้ได้)
- สร้าง Model
Users
โดยเป็นตัวแทนของ tableusers
- สร้าง Model
Addresses
โดยเป็นตัวแทนของ tableaddresses
- และทำการบอก relation ระหว่าง
Users
และAddresses
เมื่อเราเพิ่ม Schema เสร็จให้ปลด comment ตรง sequelize.sync()
ออก เมื่อเราปลดเสร็จและลอง run ดู เราจะเจอตารางที่สร้างเสร็จเรียบร้อยใน phpmyadmin
ผลลัพธ์จาก phpmyadmin
มาลองทำ CRUD ผ่าน ORM กัน
เรามาทำ API ทั้ง 5 เส้นนี้กันคือ
GET /users
สำหรับ get users ทั้งหมดที่บันทึกเข้าไปออกมาPOST /users
สำหรับการสร้าง users ใหม่บันทึกเข้าไปGET /users/:id/address
สำหรับการ get address ทั้งหมด รายคนออกมาPUT /users/:id
สำหรับการแก้ไข users รายคนและเพิ่ม address เข้าไปDELETE /users/:id
สำหรับการลบ users รายคน (ตาม id ที่บันทึกเข้าไป)
1. GET /users
สำหรับ get users ทั้งหมดที่บันทึกเข้าไปออกมา
- เปลี่ยนจากการใช้ query select ตรงๆเป็นทำผ่าน model
User
(ที่เป็นตัวแทนของ table user ออกมาแทน) - ผ่านคำสั่ง
User.findAll()
2. POST /users
สำหรับการสร้าง users ใหม่บันทึกเข้าไป
- ใช้คำสั่ง
User.create
ทำการ insert ข้อมูลเข้าไปได้เลย (จะเหมือนกับ SQL INSERT)
3. GET /users/:id/address
สำหรับการ get address ทั้งหมด รายคนออกมา
- ใช้คำสั่ง
findAll
หรือfindOne
ในการค้นหาโดยใช้{ where: ... }
เข้ามาเป็น condition เพิ่ม - และใช้
{ include: ... }
เข้ามาสำหรับทำ JOIN table (ดึง table ที่เป็น relation มา) - หากอย่าง breakdown แต่ละอันออกมา (เป็นเหมือน LEFT JOIN) ให้ใส่
raw: true
เข้ามา
4. PUT /users/:id
สำหรับการแก้ไข users รายคน (ตาม id ที่บันทึกเข้าไป) และเพิ่ม address เข้าไป (ถ้ามี field address นั้น)
- ใช้ท่า
upsert
ของ SQL สำหรับการใส่ข้อมูล upsert
คือท่าที่จะเช็ค primary key ของข้อมูลก่อน insert เข้าไปว่า- ถ้ามีอยู่แล้ว =
update
กลับผ่าน primary key ตัวเดิม - ถ้าไม่มี =
insert
เป็นค่าใหม่เข้าไป
- ถ้ามีอยู่แล้ว =
Ref: https://sqlperformance.com/2020/09/locking/upsert-anti-pattern
5. DELETE /users/:id
สำหรับการลบ users รายคน (ตาม id ที่บันทึกเข้าไป)
- ใช้คำสั่ง
User.destroy
ในการลบข้อมูลได้ - หาก schema ด้านบน เป็น
User.hasMany(Address, { onDelete: 'CASCADE' })
= ตอนลบข้อมูลออกจาก User, Address (ที่มี relation กับ User ผ่าน userId) หากข้อมูลใดมี userId เดียวกัน = โดนลบด้วย (จะลบคู่กันไปเลย) - หากไม่ใส่ = ข้อมูลไม่หาย และ userId ใน Address จะเป็น null แทน
เปลี่ยนมาลองกับ PostgresSQL
ข้อดีของการใช้ ORM อีกอย่างหนึ่งคือ มันเป็นตัวกลางที่สามารถเปลี่ยน Database เป็นตัวอื่นก็ได้ (ไม่จำเป็นต้องเป็น MySQL อย่างเดียว) เช่นเคสนี้ เราจะลองเอามาต่อกับ PostgresSQL แทน
ที่ express ลองเปลี่ยนมาต่อเป็น sequelize ก็จะได้ผลลัพธ์เหมือนเดิมออกมาได้ผ่านการยิง API
ผลลัพธ์จากการลองสร้าง user (เปิดผ่าน pgadmin)