7 คะแนน โดย GN⁺ 2023-06-28 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ORM มักถูกวิจารณ์ว่าเป็น “ของเล่นสำหรับสตาร์ตอัป” แต่ประเด็นจริงมักใกล้เคียงกับ วิธีใช้งานที่ผิด มากกว่าตัว ORM เอง
  • object graph กับ relational table เป็นคนละโมเดลกัน จึงเกิด paradigm mismatch แต่เพียงเท่านี้ก็ยังยากจะสรุปว่า ORM เป็น anti-pattern
  • ข้อถกเถียงเรื่องการละเมิด SRP และ SOC ยังมีน้ำหนักอยู่ แต่ ORM เดิมทีก็เป็น abstraction ที่เชื่อมข้อมูลสองโมเดลเข้าด้วยกัน จึงตัดสินได้ยากจากแค่การละเมิดหลักการ
  • ปัญหาด้านประสิทธิภาพมักไม่ได้เกิดจาก ORM โดยตรง แต่เกิดจากแพตเทิร์นที่นำข้อมูลมาประกอบกันใน ตรรกะของภาษา host มากกว่า และลดได้ด้วย ความสามารถคล้าย SQL ของ ORM
  • เหตุผลหลักที่ Lago เปลี่ยน Active Record query ไปเป็น raw SQL ไม่ใช่เรื่องความเร็ว แต่เป็นเรื่อง การมองเห็นได้ชัดเจนและการดีบัก และสำหรับ query ขนาดใหญ่ การย้ายไปใช้ raw SQL อาจคุ้มค่ากว่าในทางปฏิบัติ

มุมมองพื้นฐานต่อคำวิจารณ์ ORM

  • ORM มักถูกโจมตีว่าเป็น “anti-pattern” แต่ข้อสรุปว่า ORM เองไม่ดีนั้นค่อนข้างเกินจริง
  • เช่นเดียวกับ abstraction อื่น ๆ ORM ก็ไม่ได้สมบูรณ์แบบ และอาจทำให้เกิด การสูญเสียความชัดเจนในการมองเห็น และ ต้นทุนด้านประสิทธิภาพ บางส่วน
  • Lago เคยพึ่งพา Active Record ซึ่งเป็น ORM ของ Ruby on Rails แล้วพบปัญหา จึงใช้โอกาสนี้กลับมาทบทวนแนวทางการพึ่งพา ORM ใหม่
  • บทสรุปไม่ได้ไปในทางว่าควรทิ้ง ORM แต่ใกล้เคียงกับการมองว่า ORM เป็น abstraction ที่มีทั้งข้อดีและข้อเสียชัดเจน

ความแตกต่างระหว่าง object model กับ relational database

  • object ที่ ORM จัดการมีลักษณะใกล้เคียงกับ directed graph ที่ชี้ไปยัง node อื่น
  • ตารางใน relational database มีลักษณะใกล้เคียงกับ undirected graph ที่ข้อมูลเชื่อมถึงกันแบบสองทางผ่าน shared key
  • ORM สามารถเลียนแบบ undirected graph ได้ แต่การตั้งค่าและพฤติกรรมไม่ได้เรียบง่ายเสมอไป
    • object User อาจไม่มีอาร์เรย์ Posts
    • entity ในอาร์เรย์ Posts อาจไม่มี reference ย้อนกลับไปยัง object User เดิม
    • ต่อให้มี reference ย้อนกลับ ก็อาจไม่ใช่ object เดิม แต่อาจเป็น object ที่ถูกคัดลอกขึ้นมา
  • ตัวอย่างเช่น ORM อาจคืนโพสต์ของผู้ใช้มาเป็นอาร์เรย์ แต่ไม่ได้ใส่ user back-reference ในบทบาทผู้เขียนไว้ในแต่ละโพสต์
  • ความไม่สอดคล้องนี้เป็น คำวิจารณ์เชิงวิชาการ ที่ดีต่อ ORM แต่ในระบบจริง ปัญหาที่ใหญ่กว่ามักปรากฏจากรูปแบบการใช้งานที่เฉพาะเจาะจง

ข้อถกเถียงเรื่อง SRP และ SOC

  • Single Responsibility Principle (SRP)

    • ORM ถูกวิจารณ์ว่าละเมิด Single Responsibility Principle (SRP)
    • เพราะมันอาจทำให้ class หรือ layer เดียวต้องรับหลายบทบาทพร้อมกัน
      • ทำงานกับฐานข้อมูลและ transaction
      • แสดงแทน record
      • นิยามความสัมพันธ์
      • สร้างและรัน migration
    • แต่เนื่องจากจุดประสงค์ของ ORM คือการเชื่อม data paradigm ที่ต่างกันสองแบบ จึงยากจะปฏิเสธ ORM เพียงเพราะมีการละเมิดหลักการบางส่วน
  • Separation of Concerns (SOC)

    • ORM อาจทำให้หลัก Separation of Concerns (SOC) เลือนรางลงด้วย
    • SOC คือหลักที่ว่าองค์ประกอบของโครงสร้างพื้นฐานควรมี concern เดียว
    • ORM ย้ายการจัดการฐานข้อมูลเข้ามาอยู่ฝั่ง backend มากขึ้น จึงอาจมองได้ว่าเป็นการละเมิด SOC
    • อย่างไรก็ตาม ปัจจุบันองค์ประกอบโครงสร้างพื้นฐานและ coding pattern มักผสานหลายบทบาทเข้าด้วยกันเพื่อประสิทธิภาพ latency และการจัดระเบียบโค้ด
      • CPU aggregator ภายในฐานข้อมูล OLAP
      • edge backend-frontend
      • monorepo

ปัญหาด้านประสิทธิภาพมักเกิดจากวิธีใช้มากกว่าตัว ORM

  • คำวิจารณ์ที่ว่า ORM ไม่มีประสิทธิภาพเสมอนั้น โดยมากไม่ถูกต้อง
  • ORM หลายตัวทำงานได้มีประสิทธิภาพกว่าที่นักพัฒนาคิดไว้มาก
  • ปัญหาคือ ORM ทำให้การนำข้อมูลมาประกอบกันด้วย ตรรกะของภาษา host อย่าง JavaScript หรือ Ruby เป็นเรื่องง่าย
  • ในตัวอย่างของ TypeORM รูปแบบที่ไม่ดีคือการดึงผู้เขียนของบริษัทหนึ่งมาก่อน จากนั้นค่อยดึงโพสต์ของผู้เขียนแต่ละคนอีกครั้ง และบันทึกแต่ละโพสต์แยกกัน
  • วิธีที่ดีกว่าคือใช้ createQueryBuilder() ของ TypeORM เพื่อประกอบ update query เดียว
  • ในการรีแฟกเตอร์ SQL สำหรับระบบ billing ของ Lago ก็ ไม่มีความต่างด้านประสิทธิภาพ ระหว่างเวอร์ชัน Active Record กับ raw SQL
    • เพราะ query เดิมก็ถูก optimize ไว้แล้วจากการใช้ความสามารถในการรวมข้อมูลของ Active Record อย่างมาก
    • การเขียนใหม่เป็น raw SQL จึงใกล้เคียงกับการเพิ่มความชัดเจนในการมองเห็น มากกว่าการเพิ่มประสิทธิภาพ

กรณีที่ ORM อาจช้าลงได้จริง

  • ORM ไม่ได้มีประสิทธิภาพเทียบ raw SQL เสมอไป และในบางสถานการณ์อาจไม่มีประสิทธิภาพอย่างมาก
  • ปัญหาแรกคือ ORM อาจสร้าง overhead ในการคำนวณ สูงมากตอนแปลงผลลัพธ์ของ query ให้เป็น object
    • TypeORM ถูกยกเป็นตัวอย่างของปัญหานี้
  • ปัญหาที่สองคือกรณีวนผ่านความสัมพันธ์แบบ one-to-many หรือ many-to-many แล้วส่งคำขอไปกลับฐานข้อมูลหลายครั้ง
  • แพตเทิร์นนี้เรียกว่า ปัญหา N+1
    • จาก query หลัก 1 ครั้ง มี subquery เพิ่มอีก N ครั้ง
    • ในตัวอย่างของ Prisma หากดึง user, post และ comment แบบซ้อนกัน อาจเกิดคำขอฐานข้อมูลใหม่สำหรับแต่ละ comment
  • N+1 พบได้บ่อยใน ORM แต่หากใช้ data loader ก็สามารถยุบ query จาก N+1 ครั้งให้เหลือ 2 ครั้งได้
  • ปัญหาทั่วไปของ ORM จำนวนมากสามารถหลีกเลี่ยงได้ หากใช้ความสามารถของ ORM ได้เต็มที่เพียงพอ

ปัญหาที่ใหญ่กว่าคือความชัดเจนในการมองเห็นและการดีบัก

  • จุดอ่อนที่สุดของ ORM คือ ความชัดเจนในการมองเห็น
  • เพราะ ORM แท้จริงแล้วเป็น query builder ดังนั้นยกเว้นกรณีอย่าง type error ที่ชัดเจนของ raw type มันจึงไม่ใช่ตัวส่งต่อข้อผิดพลาดปลายทางโดยตรง
  • เมื่อ SQL error ถูกส่งกลับมา ORM ต้องตีความแล้วส่งต่อให้ผู้ใช้อีกทอดหนึ่ง
  • Active Record มีปัญหาในจุดนี้ และนี่ก็เป็นเหตุผลที่ Lago รีแฟกเตอร์ query สำหรับ billing subscription
  • เมื่อได้ผลลัพธ์ไม่ตรงคาด ต้องวนทำกระบวนการต่อไปนี้ซ้ำ
    • ตรวจดู SQL query ที่ render ออกมา
    • รัน SQL นั้นใหม่อีกครั้ง
    • ย้อน SQL error กลับไปเป็นการแก้ไขฝั่ง Active Record
  • การไปกลับแบบนี้ทำให้เป้าหมายเดิมของการใช้ Active Record เพื่อไม่ต้องโต้ตอบกับฐานข้อมูล SQL โดยตรงอ่อนลง

การตัดสินใจเชิงปฏิบัติ

  • หากใช้อย่างถูกต้อง ORM สามารถให้ประสิทธิภาพใกล้เคียง raw SQL ได้
  • ปัญหาจำนวนมากเกิดขึ้นเมื่อไม่ใช้ความสามารถคล้าย SQL ของ ORM และพึ่งโครงสร้างตรรกะของภาษา host มากเกินไป
  • หาก query ขนาดใหญ่ทำให้นักพัฒนาทำงานลำบากและขัดขวางการดีบัก การย้ายไปใช้ raw SQL query อาจเป็นการลงทุนที่ดี
  • ORM ส่วนใหญ่มีความสามารถให้รัน SQL query ภายใน ORM ได้

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

 
GN⁺ 2023-06-28
ความเห็นจาก Hacker News
  • ฉันใช้ ORM มาตั้งแต่สมัยที่ Hibernate เพิ่งออกมาใหม่ ๆ ใน Java แต่ก็ไม่ค่อยชอบมันมาตลอด
    ข้อดีที่ว่า “สามารถเปลี่ยนไปใช้ฐานข้อมูลตัวอื่นได้” พอมองตอนนี้ก็แทบเป็นคำพูดลอย ๆ เพราะในความเป็นจริงไม่มีใครใช้แบบนั้น
    คำพูดที่ว่า “ไม่จำเป็นต้องรู้ SQL” ก็ไม่จริง และแอปที่ไม่ trivial และอยู่ยาว สุดท้ายก็ต้องมาปรับแต่ง query รายตัวในระดับสตริงอยู่ดี
    สำหรับ data layer วิธีที่ถูกต้องคือสร้างและประกอบ query ทีละตัวด้วย SQL แบบสตริง และยิ่งใกล้กับ raw JDBC เท่าไรก็ยิ่งดีกว่าในมุมมองของฉัน
    คำอ้างว่า ORM สนับสนุน “domain model” ก็เป็นเหตุผลแย่ ๆ เช่นกัน เพราะ domain model แบบนั้นสุดท้ายจะกลายเป็น anemic domain model ที่ไม่มี logic อยู่เสมอ
    พอคิดถึงเวลาที่เสียไปกับ ORM, XML, annotation และการ debug SQL ที่ถูก generate ออกมา ก็อยากจะร้องไห้

    • เห็นด้วยมาก อยากอธิบาย ORM ว่า “มันทำให้ส่วนที่ง่ายอยู่แล้วง่ายขึ้นอีกนิด แต่ทำให้ส่วนที่ยากยากขึ้นแบบสุด ๆ”
      ส่วนที่ง่ายมันก็ง่ายอยู่แล้วเลยไม่ได้สนใจมาก แต่สิ่งที่เจอบ่อยคือในช่วงเลวร้ายที่สุด ตอนที่ query พังเมื่อระบบเริ่มมีขนาดใหญ่ งานกลับยากขึ้นเป็นล้านเท่าเพราะต้องมานั่งสู้กับ ORM เอง
      query builder อาจไม่เหมือน ORM เสียทีเดียว แต่ฉันชอบบทความ https://gajus.medium.com/stop-using-knex-js-and-earn-30-bf41... และกลายเป็นแฟนของเทคโนโลยีสาย Slonik
      มันให้ความสามารถที่ต้องการราว 90% เช่น bind ผลลัพธ์เข้ากับ object อัตโนมัติและตรวจสอบ type อย่างเข้มงวด ขณะเดียวกันก็ยังเปิดให้ใช้ SQL แบบธรรมดาได้
      ฉันก็ไม่เข้าใจเหมือนกันว่าทำไมคนถึงคิดว่า SQL เป็นภาษาที่ลึกลับและยากเย็น มันอาจเก่าและมีข้อบกพร่อง แต่ก็ยังดีกว่าไปเรียนรู้ภาษา query เฉพาะทางที่เครื่องมือ ORM สร้างขึ้นมา
    • ฉันไม่ชอบ ORM มาตั้งแต่ครั้งแรกที่ลองใช้เมื่อนานมาแล้วด้วยเหตุผลเดียวกัน และมองว่ามันเป็น anti-pattern
      ตอนนั้นถ้าพูดคำวิจารณ์แบบนี้ นักพัฒนาคนอื่นมักจะมองลงมาใส่
      แม้แต่ตอนนี้ก็ยังมีเครื่องมือและเทคนิคจำนวนมากที่ในฐานะนักพัฒนาต้องฝืนใช้ทั้งที่เป็น anti-pattern และความจริงอันน่าเศร้าของวงการก็คือ เครื่องมือที่ถูกใช้อย่างแพร่หลายจำนวนไม่น้อยนั้นไม่ค่อยดีและยิ่งส่งเสริม anti-pattern
      ในอุตสาหกรรมเทคโนโลยีมีนักพัฒนาและอดีตนักพัฒนาจำนวนน้อยที่มีเส้นสายและถืออำนาจในการตัดสินใจ ซึ่งพวกเขาก็ให้ความรู้สึกว่าไม่ค่อยดีนัก
    • คุณดันเริ่มต้นกับ ORM ที่เรียกได้ว่าแย่ระดับต้น ๆ ที่มีอยู่พอดี
      ก็มี ORM ที่สัดส่วนข้อดีข้อเสียค่อนข้างโอเคอยู่เหมือนกัน โดยเฉพาะฝั่ง Python
      Django ORM อาจจะหยาบ ๆ และประสิทธิภาพไม่เด่น แต่ผสานรวมได้ดี ใช้งานได้จริง productive และประสบการณ์ใช้งานก็ดี
      Peewee เป็นแพ็กเกจเล็ก ๆ ที่ให้ ORM มาสำหรับเวลาที่ไม่ได้ต้องการฟีเจอร์ยิ่งใหญ่อะไรมาก และแค่อยากเขียนโปรแกรมเล็ก ๆ แบบสบาย ๆ ไม่ให้หงุดหงิด
      SQLAlchemy ต้องลงทุนเรียนรู้มากกว่า แต่ยืดหยุ่นมาก สร้าง SQL ที่สะอาด และทำงานได้ถูกต้อง
      มันยังเปิดให้ใช้ query builder ระดับล่างกว่าด้วย สำหรับเวลาที่ไม่ต้องการแนวคิดเชิงวัตถุ แต่ต้องการ abstract พฤติกรรม SQL แบบ idiomatic มาแสดงใน Python
      สุดท้ายมันก็เป็นการตัดสินใจทางวิศวกรรมทั่วไปที่ต้องชั่ง ผลตอบแทนต่อการลงทุน
    • คำพูดที่ว่า “สุดท้ายก็ต้องปรับในระดับสตริง” จากประสบการณ์ของฉันถือว่าไร้สาระ
      ฉันทำมาเกิน 10 ปีแล้ว และไม่เคยมี query ไหนที่อ่านเอกสาร Hibernate แค่ 5 นาทีแล้วทำตามแนวทางสำหรับกรณีนั้นจะจัดการไม่ได้ดีกว่า
      ดูเหมือนว่าเหล่า แฟนบอย SQL ที่เชื่อว่านักพัฒนาทุกคนต้องท่องตารางตรรกะสามค่าและชื่อฟังก์ชันสไตล์ COBOL กลับไม่ยอมเสียแรงอ่านเอกสาร Hibernate เสียมากกว่า
    • ไม่เคยมีใครที่มีวิจารณญาณแม้แต่นิดเดียวอ้างว่า “ไม่จำเป็นต้องรู้ SQL” เป็นข้อดีของ ORM
      ORM มีไว้สำหรับ OLTP workload ไม่ใช่ OLAP และหนึ่งในผู้ก่อตั้ง Hibernate ก็เคยพูดแบบนั้น
      ประโยชน์หลักมีแค่ช่วยไม่ต้องเขียน query INSERT/UPDATE ยาว ๆ ที่ผิดพลาดได้ง่ายด้วยมือ และช่วย map row ของ SQL ไปเป็น object แบบอัตโนมัติ
      ถ้าเลือกไปทาง raw คุณก็ต้องกลับไปเขียนหลายส่วนพวกนั้นขึ้นมาใหม่เอง ดังนั้นมันก็ไม่ใช่เกมที่ผลรวมเป็นศูนย์อยู่ดี
  • ในแอปที่ซับซ้อนมากพอ สุดท้ายก็มักจะมีบางอย่างอย่าง query builder เกิดขึ้น เพื่อให้หลายส่วนของแอปร่วมกันประกอบ query แบบหน่วงเวลาได้
    โครงสร้างจะกลายเป็นการประกอบเป็น query แทนที่จะเอาข้อมูลมารวม กรอง และเรียงลำดับกันในหน่วยความจำ
    ORM เป็นเครื่องมือที่ช่วยให้ทำแบบนี้ได้ง่าย แต่ก็อาจทำให้เผลอสร้าง N+1 query โดยไม่รู้ตัวจนเจ็บตัวได้เช่นกัน
    เหมือนเครื่องมือทุกชนิด ต้องชั่งน้ำหนัก trade-off กับกรณีใช้งานจริง และไม่แนะนำวิธีจัดการสตริงคำสั่ง SQL โดยตรงไม่ว่าในกรณีใดก็ตาม

    • ประสบการณ์กับ ORM อาจต่างกันสุดขั้วตามภาษา ระดับการผสานรวม และประเภทงานที่ใช้
      ถึงอย่างนั้นก็ยังมีคนที่มองข้ามความต่างเหล่านั้นแล้วพูดแบบเหมารวมว่า ORM ดีหรือแย่เสมอ
      แบบนั้นมองว่าเป็นท่าทีชวนถกเพื่อเรียกความสนใจแล้วข้ามไปดีกว่า และอยากโฟกัสกับการคุยที่เริ่มจากว่า “ORM มีข้อดี แต่ต้องรู้ว่ามันใช้ได้ผลตรงไหน และต้องไม่ให้ปัญหาในการใช้งานมีมากกว่าข้อดี”
    • ไม่ได้มองว่าแอปที่ซับซ้อนพอจะต้องมี query builder เป็นเรื่องธรรมดา และไม่ยอมรับแนวคิดนั้นในแอปของตัวเอง
      ถ้าต้องการข้อมูลก็ไปที่ data access layer และถ้ามันยังไม่ให้สิ่งที่ต้องการ ก็สร้าง repository หรือ provider ใหม่ตามแพตเทิร์นที่ใช้อยู่
      ไม่ว่าจะเป็น ORM หรือเครื่องมือประกอบ query ที่ทำเอง ถ้าแอปเริ่มทำให้เราหลงคิดว่า “มันซับซ้อนพอจนต้องมีสิ่งนี้” แปลว่ามันซับซ้อนเกินกว่าจะใช้งานอย่างมั่นคงแล้ว
      สุดท้ายก็มักไปประเมิน builder ผิดชั้น หรือพลาดไม่ทันสังเกตว่ากำลังสร้าง N+1 query อยู่ตอนวนลูปผลลัพธ์
      ORM ไม่จำเป็น
    • มองว่านี่ใกล้เคียงกับสิ่งที่ LINQ จากปี 2007 ทำ
      LINQ ไม่ใช่ ORM ตรง ๆ และเมื่อสร้างคำสั่ง LINQ แบบ strongly typed มันจะถูกแปลงเป็น expression tree แล้วค่อยให้ query provider แปลงต่อเป็น query
    • ในฐานะคนที่ใช้ทั้ง ORM และ raw SQL ก็เข้าใจทั้งสองฝั่ง
      ระหว่างพัฒนาจะชอบความอ่านง่ายของ ORM ในตัว model แต่เมื่อจำเป็นก็จะคอยจับตา N+1 และความไม่มีประสิทธิภาพอื่น ๆ อย่างใกล้ชิดแล้วค่อยปรับให้เหมาะสม
      เส้นแบ่งของคำว่า “เมื่อจำเป็น” ไม่ชัดนัก แต่ก็พยายามไม่ optimize เร็วเกินไป
      เวลาขึ้นโปรเจกต์ใหม่มักเริ่มจากฐานข้อมูล และก่อนเขียนโค้ดจะใช้ factory, seeder, raw SQL query เพื่อทำความเข้าใจกับ data model ก่อน
      ORM อย่าง Eloquent ของ Laravel มีเมธอดดี ๆ สำหรับแก้ปัญหา N+1 และทำ lazy loading แต่ก็มี trade-off เสมอ
    • ยังมีไลบรารีที่กลับแนวคิด ORM ไปอีกทาง
      แทนที่จะเป็นไลบรารีที่ยอมให้ประกอบ query แบบหน่วงเวลา ถ้าเขียน query ลงไปตรง ๆ ไลบรารีจะสร้างโค้ดสำหรับ query นั้นตอนคอมไพล์
      เช่น ถ้าเขียน query อย่าง getUser: SELECT * FROM users WHERE id = ? มันก็จะสร้างคลาส GetUserQuery และเมธอดอย่าง getUser(id: String): GetUserQueryResult ให้ และมองว่าโมเดลแบบนี้ดีกว่ามาก
  • เหตุผลหลักที่ไม่ชอบ ORM คือมันมีแนวโน้มจะปฏิบัติต่อ SQL engine สมัยใหม่เหมือนเป็น ที่เก็บบิตโง่ ๆ ที่ถูกทำให้เกินจริง
    ทั้งที่ CTE, LATERAL join, และ RETURNING clause สามารถทำให้การประมวลผลง่ายขึ้นมากหรือกำจัดความเป็นไปได้ของข้อมูลไม่สอดคล้องกันได้ แต่ ORM ส่วนใหญ่ก็ยังหยุดอยู่แค่การแมปตารางพื้นฐานและวิวเข้ากับอ็อบเจ็กต์ที่กำหนดไว้ล่วงหน้าแบบง่าย ๆ
    ถ้า ORM สร้างตารางให้ด้วยก็ยิ่งแย่
    SQL โดยแก่นแท้เป็นทั้งเครื่องมือดึงข้อมูลและ ภาษาเพื่อการแปลงข้อมูล แต่ ORM แทบไม่สนใจด้านนี้เลย จนทำให้นักพัฒนาจำนวนมากไม่รู้ว่าใน SQL ยังมีอะไรนอกจาก INSERT/SELECT/UPDATE/DELETE
    ยังมี pivot table, temporal query, CUBE/ROLLUP, window function, set-returning function, materialized view, external table, การจัดการ JSON, การจัดการวันที่, exclusion constraint, ชนิดข้อมูลอย่าง range·interval·domain, row-level security, MERGE และอื่น ๆ
    เหมือนมีโรงเก็บเครื่องมือครบชุด แต่มีคนยื่นให้แค่ค้อน ไขควง และเลื่อยเหล็กอย่างละอัน พร้อมกล่อมว่าพอแล้ว ทั้งที่โต๊ะเลื่อย เราเตอร์ เครื่องขัด และชุดลิ่มก็อยู่ห่างไปไม่กี่เมตร และคุณอาจใช้เวลาทั้งอาชีพโดยไม่รู้เลยว่ามันมีอยู่

    • เฟรมเวิร์ก ORM ส่วนใหญ่มีความสามารถในการรัน raw SQL อยู่แล้ว เพราะฉะนั้นการใช้ ORM ไม่ได้แปลว่าจะใช้ความสามารถพวกนี้ไม่ได้
      ORM มักทำให้คนคิดอยู่กับคำสั่ง CRUD ง่าย ๆ เป็นหลัก แต่ก็มองว่านั่นอาจจะดีกว่า และควรใช้ความสามารถขั้นสูงอย่างประหยัด
    • รู้สึกว่าตัวเองโชคดีที่ทำโปรเจกต์ซึ่งไม่มีฐานข้อมูลเลยเป็นส่วนใหญ่
      ถึงอย่างนั้นถ้าต้องใช้ DB ก็จะตั้งใจใช้แค่ ส่วนย่อยเล็กมากของความสามารถ DB เท่านั้น
      เพราะมองว่า “โค้ด” เป็นพื้นที่ที่ควบคุมได้ ขณะที่ “ฐานข้อมูล” เป็นพื้นที่เสี่ยง เข้าถึงยาก และข้อผิดพลาดมักไปโผล่ทีหลังในที่อย่าง integration test ที่ช้า
      logic ที่เกิดขึ้นใน DB อยู่นอกขอบเขตของ low-level test เลยทำให้น่ากลัว และคงไม่ใช้แม้แต่ query แบบ select-from-select
      เครื่องมือส่วนใหญ่ในโรงเก็บนั้นยินดีจะปล่อยทิ้งไว้
    • เพราะแบบนี้ก็ไม่แปลกใจที่นักพัฒนาจำนวนมากมองไม่ออกถึงความต่างระหว่าง SQL และ NoSQL
    • เมื่อสเกลใหญ่ขึ้น ก็มักมีการ denormalize ฐานข้อมูลอย่างหนักเพื่อเหตุผลด้านประสิทธิภาพ จนใช้งานมันเหมือนเป็นที่เก็บบิตโง่ ๆ ที่ถูกทำให้เกินจริง
    • เคยทำหลายโปรเจกต์ที่พูดว่า “เราไม่ต้องใช้ความสามารถฐานข้อมูลหรู ๆ พวกนั้น” และการรับมือกับผลที่ตามมานั้นน่าอึดอัดมาก
      เวลาส่วนใหญ่หมดไปกับการสร้างทางอ้อมที่ซับซ้อนเพื่อประดิษฐ์กลไกปกป้องความถูกต้องของข้อมูลพื้นฐานขึ้นมาใหม่
      การสร้างฐานข้อมูลของตัวเองอาจสนุกในเชิงการเรียนรู้ แต่ไม่ใช่สำหรับแอป production
  • ActiveRecord ให้ความรู้สึกดีมาโดยตลอด
    แม้จะมีข้อยกเว้นอยู่บ้าง แต่ก็จับจุด 80/20 ได้ดี ถ้าจำเป็นก็หนีไปใช้ SQL ได้ และ lifecycle hook ที่ช่วยจัดระเบียบ domain logic ก็ยอดเยี่ยมและเข้าใจง่าย
    ORM เชิงปฏิบัติที่ไม่ซ่อนทุกอย่างไว้หลัง “bundle” หรือไม่เอาภาษา query ของตัวเองเข้ามานั้นเป็นเรื่องที่ดี
    ORM ที่พยายาม “แก้” ปัญหา ORM ให้หมดจดต่างหากที่ใช้งานยาก

    • ActiveRecord ใช้งานสนุกจริง ๆ และถ้าเจอ edge case ก็กลับไปใช้ raw SQL ได้ทุกเมื่อ
      ORM ควรถูกสร้างแบบนี้
    • ยากจะจินตนาการถึงการสร้างแอป CRUD ขนาดใหญ่ใหม่อีกครั้งโดยไม่มี ActiveRecord เพราะความอ่านง่ายและการบำรุงรักษาจะลดลง
      แต่ถ้าจะใช้ให้ถูกต้องก็ต้องมีพื้นฐาน SQL ที่ดีล่วงหน้า
      ปัญหาเกิดขึ้นตอนที่คนไม่เรียน SQL และไม่เข้าใจ query ที่ AR สร้างขึ้นรวมถึงข้อจำกัดของมัน
    • ไม่ค่อยเข้าใจคำบ่นเกี่ยวกับ ORM เท่าไร อาจเพราะ ORM เดียวที่เคยใช้คือ ActiveRecord
  • สมมติฐานตั้งต้นผิด ORM ไม่เคยเป็น “แนวคิดที่เป็นอันตราย” มาก่อน
    มี ORM ที่ทำงานด้วยแล้วน่าปวดหัวอยู่จริง แต่ไม่ได้แปลว่าทั้งแนวทางนี้เป็นความคิดที่แย่
    ใช้ SQLAlchemy อย่างมีความสุขมาหลายปี เพราะมันไม่ได้พยายามสร้างแนวคิดของ SQL ขึ้นมาใหม่
    แต่มันให้ชั้นที่สะดวกสำหรับสร้างและส่งต่อคิวรี พร้อมจัดการรายละเอียดจุกจิกต่างๆ ได้ดี ทำให้ไม่ต้องไปง่วนกับมันเอง

    • ประสบการณ์กับ ORM ของผมแทบทั้งหมดมีแค่ SQLAlchemy และเคยลองเรียน Ecto นิดหน่อย พอมาเห็นเธรดนี้ก็เลยสงสัยว่า ORM อื่นๆ มันแย่กันหมดเลยหรือเปล่า
      ช่วงหลังระหว่างเรียน Rust รู้สึกว่าการต้องเขียนโค้ด boilerplate สำหรับจัดการ CRUD ง่ายๆ เองนั้นทั้งทรมานและเสียเวลา
      ORM ของ SQLAlchemy อาจเกะกะบ้างกับคิวรีที่ซับซ้อนกว่าหรือต้องการ optimize แต่ในกรณีนั้นก็ลงไปเขียน SQL ตรงๆ ได้เสมอ
    • นานมากแล้วที่ได้เห็นคนใช้สำนวน “begging the question” ได้ถูกต้องจริงๆ
    • ขอแนะนำ SQLAlchemy ตรงที่มันทำให้เขียน SQL ได้อย่างมั่นใจ ในแบบที่โค้ดสอดคล้องไปกับคิวรี
  • เหมือนกับทุกอย่าง มันมีทั้งกรณีที่เหมาะและไม่เหมาะ และเทคโนโลยีก็ถูกใช้เกินเลยหรือใช้ผิดทางได้ง่าย
    ตัวอย่างเช่น ORM อย่าง Entity Framework ของ .NET อาจเป็นทรัพย์สินชิ้นใหญ่ได้ โดยเฉพาะเวลาทำงานแบบ database-first
    ถ้าเป็น DB ที่มีหลายร้อยตารางและมีความสัมพันธ์ foreign key ที่ถูกต้อง ORM จะช่วยในการสำรวจสคีมาที่ซับซ้อนและให้การตรวจสอบตอนคอมไพล์เมื่อมีการเปลี่ยนแปลง
    SQL แบบฝังในโค้ดเป็นดาบสองคมที่ดูเหมือนมีข้อได้เปรียบด้านประสิทธิภาพ แต่โดยเฉพาะกับ join แล้ว SQL ยาวๆ ที่ hardcode ไว้นั้นดูแลรักษายากมาก
    ไม่ได้พูดถึงตอนมี 5~10 ตาราง แต่พูดถึงตอนมีเป็นร้อยตาราง
    ไม่ได้ตั้งใจจะโน้มน้าวใคร แต่ก็ไม่ควรเชื่อทุกอย่างที่อ่านเจอในอินเทอร์เน็ตเรื่อง best practice แบบตรงๆ
    คนอาจกำลังพูดถึงคนละสิ่งกันโดยสิ้นเชิง และไม่ว่าจะใช้เทคโนโลยีอะไร ก็ย่อมมีนักพัฒนาที่ทำงานห่วยอยู่เสมอ

  • ถ้าไม่ใช้ ORM หรือ DB interface อื่นๆ สุดท้ายคุณก็จะต้องเขียนและดูแลของตัวเองขึ้นมาสำหรับเรื่องอย่าง CRUD
    และมันมีโอกาสสูงที่จะมีบั๊กแปลกๆ edge case และช่องโหว่ด้านความปลอดภัยแฝงอยู่ข้างใน
    การสอนหลักการอย่าง SOLID ในชั้นเรียนมหาวิทยาลัยในฐานะหลักการออกแบบซอฟต์แวร์ก็มีปัญหาเหมือนกัน
    ในแวดวงวิชาการมันฟังดูสมเหตุสมผล เพราะข้อจำกัดอย่างเวลา ความง่ายในการดูแลรักษา เงิน และแรง ไม่ใช่เรื่องสำคัญเท่าไร แต่พอบัณฑิตจบใหม่เข้าสู่การทำงานจริงก็มักเกิดการปะทะอย่างหนัก
    เพราะคุ้นชินกับการเสียเวลาทั้งวันไปกับการเล็มขนจามรีและประดิษฐ์ล้อขึ้นใหม่เพื่อตามหาโซลูชันที่ “สมบูรณ์แบบ”
    ถ้าไม่มีซีเนียร์หรือลีดที่เก่งพอและมีจำนวนพอจะคอยห้ามไว้ คุณก็จะได้ผลิตภัณฑ์ที่น่าสนใจมาก งบบานปลาย ไฟไหม้ครั้งใหญ่ หรือไม่ก็ทั้งหมดรวมกัน
    แย่ยิ่งกว่านั้นคือเวลาของที่สร้างในโลกวิชาการและออกแบบเพื่อโลกวิชาการถูกปล่อยออกมาสู่คนทั่วไป
    ตัวอย่างที่ทั้งดีและแย่ในคราวเดียวคือ OpenStack ซึ่งมีปุ่มจับและจุดปรับแต่งมากมาย รวมถึงแยกองค์ประกอบเป็นโมดูลแบบสุดขั้วเพื่อรองรับสภาพแวดล้อมเฉพาะของ CERN และมหาวิทยาลัยอีกนับร้อย แต่ก็ซับซ้อนแบบไบแซนไทน์จนแทบเริ่มต้นใช้งานไม่ได้

    • ในเธรดนี้มีคนบ่นกันมากว่า ORM ไม่เป็นไปตามคำมั่นจากผู้ขาย และนั่นก็จริง
      ORM ไม่ได้ทำให้คุณสลับฐานข้อมูลได้โดยไม่ต้องออกแรง ไม่ได้ทำให้ไม่จำเป็นต้องเรียนรู้และเข้าใจ SQL ที่มันสร้างขึ้น และไม่ได้ทำให้สคีมาลื่นไหลไร้ปัญหา
      ถึงอย่างนั้นเหตุผลที่ยังใช้มันก็เพราะมันคือ standard abstraction ที่ยังไงก็ต้องทำซ้ำอยู่ดี
      มันทำให้การแคชการเข้าถึง DB เป็นไปได้ ซึ่งถ้าใช้ SQL เขียนมือจะยากอย่างน่าประหลาดเมื่อระบบใหญ่ขึ้น
      มันมาพร้อมเครื่องมือที่เป็นมาตรฐานและ best practice ที่ช่วยให้ปัญหาอย่าง N+1 กลายเป็นเรื่องไม่สำคัญ
      ปัญหาหลักของ ORM คือ ความคาดหวังที่ผิด และ ORM ที่ห่วย
      ถ้าอยากรู้ว่า ORM มีคุณค่าแค่ไหน ก็ลองเปิดโปรเจกต์ที่เขียนโดยไม่มี ORM แล้วลองแก้อะไรก็ได้ดู
    • ที่บอกว่า “สุดท้ายก็ต้องสร้างของตัวเอง” จะจริงก็ต่อเมื่อยืนกรานจะจัดโครงสร้าง logic ตาม object แทนที่จะเป็น “row”
      ดู https://news.ycombinator.com/item?id=36498583
    • มีทั้งหลายแผนกและหลายบริษัทที่ต้องลำบากหรือถึงขั้นล้มเหลว เพราะอัจฉริยะระดับเหนือมนุษย์หนึ่งคนหรือไม่กี่คนหมกมุ่นกับความสมบูรณ์แบบ
      งานที่มีประโยชน์ไม่เดินหน้าเลย หรือใช้เวลานานกว่าแนวทางเร็วๆ แบบเละๆ ถึง 20~100 เท่า
      ที่แย่ที่สุดคือกรณีที่ทีมดีไซเนอร์ HTML มากประสบการณ์ถูกบังคับให้เรียน functional programming
      พ่อมดตั้งค่าแบ็กเอนด์คนนั้นเป็นสิ่งมหัศจรรย์ของวิศวกรรมสมัยใหม่และการออกแบบอันงดงาม แต่ต้องใช้คน 10 คนทำอยู่ 1 ปี
      เงิน VC ทำให้คนแปลกไป
    • ได้ยินเรื่องแย่ๆ เกี่ยวกับ SQLAlchemy มากเกินไปจนตัดสินใจจะสร้าง query builder เบาๆ ขึ้นมาใช้เอง แต่ผ่านไป 8 เดือน ตอนนี้สุดท้ายก็ใช้ SQLAlchemy อยู่ดี
      ภาษา SQL นั้นซับซ้อนมาก และการสร้าง query builder ที่สมบูรณ์และมีฟีเจอร์ครบถ้วนจริงๆ ไม่ใช่เรื่องเล็กน้อยเลย
      ต่อให้ไม่นับส่วนของ relational mapping ที่แท้จริงก็ยังเป็นแบบนั้น
    • ใช้ ORM ที่ดีดีกว่า ไม่อย่างนั้นคุณก็จะต้องแบกรับครึ่งหนึ่งของ ORM ห่วยๆ เอาไว้เอง
  • มองว่า SQL เป็นกรณีที่เข้ากับคำว่า “ก็เขียนไอ้บ้าบอนั่นตรง ๆ ไปเลยสิ”
    การเรียน SQL หรือไลบรารี ORM เองไม่ได้มีความหมายมากนัก และ การเรียนและใช้ SQL โดยตรง ดีกว่า
    การเข้าถึงฐานข้อมูลน่าจะเป็นงานพื้นฐานที่ต้องทำไปตลอดทั้งอาชีพ ดังนั้นการเรียนทักษะจริงโดยตรงจึงดีที่สุด
    CSS ก็คล้ายกัน แทนที่จะเรียนไลบรารี CSS ก็ควรเรียนและใช้ CSS เอง
    จริง ๆ แล้วแทบทุกด้านของการพัฒนา frontend ก็เป็นแบบนั้น และแทนที่จะใช้ “ไลบรารีฟอร์ม” ก็จัดการ Form API ของเบราว์เซอร์โดยตรงได้เลย
    ส่วนตัวแล้วรู้สึกว่า SQL เรียนง่ายกว่า ORM ไหน ๆ มาก
    อย่าเสียต้นทุนไปกับการเรียนสิ่งที่ผิด

    • ถ้าเรียน SQL ก่อน ก็จะรู้ด้วยว่าเมื่อไรควรใช้ ORM และยังประเมิน ORM ได้ดีขึ้นด้วย
      CSS ก็เหมือนกัน ถ้ารู้ CSS เวลาจะเลือกไลบรารีมาใช้เพื่อความสะดวกก็จะยืดหยุ่นและเข้าใจได้ดีกว่ามาก
    • SQL ถูกเรียกว่าเป็น ภาษาที่เป็นนิรันดร์
      ผ่านมาทั้งเมนเฟรม เดสก์ท็อป ไคลเอนต์/เซิร์ฟเวอร์ เว็บ และภาษาอีกหลากหลายบนแพลตฟอร์มเหล่านั้น SQL ก็ยังเป็นเส้นด้ายร่วมที่มีอยู่เสมอ
      เห็นด้วยเต็มที่ว่านักพัฒนาที่เข้าใจ data layer และรู้ดีว่าเกิดอะไรขึ้นข้างล่างนั้น จะเป็นนักพัฒนาที่แข็งแกร่งกว่า
      ถ้าจะใช้เวลาไปกับการเรียนความพิสดารของ ORM ก็มีคำถามคลาสสิกว่า ทำไมไม่นำเวลานั้นไปลงทุนเรียน SQL แทน
      ผลทบต้นของประสบการณ์ SQL มีแนวโน้มว่าจะมีค่ามากกว่า ORM ของแพลตฟอร์มหรือภาษาหนึ่ง ๆ ในอีก 10~15 ปีข้างหน้า
      มีเหตุผลที่จะเรียนและใช้ ORM อยู่ แต่ไม่ควรเอนเอียงไปทาง ORM เพียงเพราะไม่อยากเรียน SQL
    • แก่นสำคัญใกล้เคียงกับคำว่า “เรียน พื้นฐานก่อน ค่อยเรียน abstraction” มากกว่า
      แบบนั้นจึงจะรู้ว่าต้องจ่ายอะไรเป็นต้นทุนให้ abstraction นั้น แน่นอน ถ้าเป็น “zero-cost abstraction” ก็อีกเรื่องหนึ่ง
    • ตั้งแต่ปี 1992 ก็เขียน SQL ตรง ๆ มาตลอดด้วย Pro*Pascal, CS130
      แต่ตอนนี้ใช้ crud ของ azer ซึ่งจัดว่าเป็น ORM เพราะเรื่อง marshalling/unmarshalling กับ Go types
      sql ในไลบรารีมาตรฐานของ Go พื้นฐานเกินไป จนแทบต้องเขียน object marshalling/unmarshalling เองทั้งหมด ซึ่งน่าเบื่อมาก
      ถ้าทุกวันนี้จะใช้ ORM ก็คงใช้เพื่อจุดประสงค์นั้นเท่านั้น
  • ชอบ micro ORM แบบ “จุดกึ่งกลาง” อย่าง Dapper หรือ Diesel
    เขียนคิวรีเป็น SQL แต่ได้ strong typing และการแมปผลลัพธ์เป็นอ็อบเจ็กต์แบบอัตโนมัติ
    เท่ากับได้ทั้ง productivity และ “ภาษาคิวรีจริง” ที่มีความสามารถครบถ้วน
    อีกแบบที่เริ่มชอบคือใช้ object mapper กับคิวรีแบบอ่านอย่างเดียวเท่านั้น
    ส่วนการอัปเดตใช้ command pattern หรือก็คือ stored procedure แล้วแมปให้ดูเหมือน native methods ได้

    • Dapper เป็นวิธีจัดการ data access ที่สนุกที่สุดเท่าที่เคยใช้มา
      นักพัฒนาไม่ควรแกล้งทำเหมือน SQL ไม่มีอยู่จริง แต่ควรยอมรับมันไปเลย
    • ชอบตรงที่ไม่ต้องเขียนฟังก์ชันของโมเดลให้สุดท้ายแมปลงไปเป็น SQL เอง หรือไม่ต้องเขียน PHP boilerplate เพื่อทำ parameterized query ให้ถูกต้อง
      Eloquent abstract งานจุกจิกพวกนั้นออกไปได้ดีพอ จึงโฟกัสกับ logic ได้
      กำลังเขียนแอป PHP “ยุคเก่า” ที่มีแค่อาร์เรย์กับคิวรีใหม่ให้เป็นโครงสร้างที่ใช้โมเดลและ Eloquent ซึ่งให้ความรู้สึกเหมือนเปลี่ยนเกมไปเลย
    • จุดกึ่งกลางนี่แหละดี ใน TypeScript ก็มีอย่างเช่น Kysely หรือ Zapatos ซึ่งเป็นโปรเจกต์ของฉันเอง
    • มองว่านี่คือ sweet spot
      สมัยก่อนตอนใช้ SQLite บน .NET ก็เคยทำไลบรารี wrapper แนวนี้เหมือนกัน: https://github.com/zmj/sqlite-fast
  • เธรดนี้แสดงให้เห็นชัดถึง การครอง mindshare ของ ORM
    ในคอมเมนต์แทบทั้งหมด ทางเลือกที่ถูกนึกถึงโดยปริยายแทน ORM คือ raw SQL ที่เป็น string literal อยู่ในโค้ดแอป
    ทั้งที่ตั้งแต่ 6 ปีก่อนก็มีเครื่องมือที่ใช้ SQL เป็นภาษาหนึ่งอย่างจริงจัง และสร้าง wrapper เพื่อ expose คิวรีเป็นเมธอดในแอปแล้ว
    เช่น queryfirst, pgtyped, pugsql, sqlc
    นี่เป็นการเปลี่ยนกระบวนทัศน์ หรืออย่างน้อยก็อาจเป็นได้ และเห็นได้ชัดว่าเป็นวิธีทำงานที่เหนือกว่า แต่ ORM กับการถกเถียงแบบเดิม ๆ ที่ไม่เปลี่ยนแปลงก็ดูดออกซิเจนไปหมด จนสิ่งเหล่านี้ยังอยู่ชายขอบ

    • ใช่เลย ใน Go ก็มี sqlc เช่นกัน
      SQL คือภาษา และยังเป็นภาษาระดับสูงมากด้วย
      การพยายามเอาภาษาระดับต่ำกว่ามาต่อแปะ SQL เป็นตัวอย่างชัดของ “ถ้ามีแต่ค้อน ทุกปัญหาก็ดูเหมือนตะปู”
      ยกตัวอย่าง ถ้าคุณเขียนโค้ดด้วย assembly ซึ่งเป็นภาษาระดับต่ำ และต้องโต้ตอบกับระบบขนาดใหญ่ โดยเอกสารของระบบนั้นบอกว่าอินพุตจะแสดงได้ด้วย JavaScript เท่านั้น คุณก็มีทางเลือก
      จะไปเอาไลบรารีพิสดารที่ช่วยให้ต่ออะไรบางอย่างขึ้นมาด้วย assembly ล้วน ๆ โดยไม่ต้อง commit ไฟล์ xxx.js เข้า repo ก็ได้ หรือจะใช้พลังของภาษาระดับสูงแล้วเขียนเป็นไฟล์ .js ไปตรง ๆ ก็ได้
    • หลังจากใช้ sqlc ในโปรเจกต์หนึ่งแล้ว รู้สึกว่าคงกลับไปแบบเดิมไม่ได้อีก
      มันทำให้มี single source of truth สำหรับ SQL query ที่ใช้งานอยู่ และเพราะสิ่งที่ต้องเขียนและดูแลจริง ๆ คือตัวคิวรีเอง การปรับแต่งอย่างการสร้างดัชนีใหม่ก็เลยง่ายขึ้น
    • ActiveRecord นั้นไม่ค่อยดีมาตลอด แต่ arel ที่ใช้ประกอบคิวรีอยู่ข้างในนั้นชอบมาโดยตลอด
      การมี abstraction อยู่บน raw SQL string นั้นมีประโยชน์มาก แต่การแกล้งทำให้ความสัมพันธ์ดูเหมือนเป็นอ็อบเจ็กต์นั้นเป็นแนวทางที่ซับซ้อนพันกันเกินไป
    • Enterprise Objects Framework ของ NeXT รองรับสิ่งนี้มาตั้งแต่กว่า 25 ปีก่อนแล้ว
      TopLink ก็น่าจะเป็นแบบนั้นเหมือนกัน
      วิธีที่มันจัดการการแมประหว่าง stored procedure กับ local entity ก็เป็นแบบนี้
    • อยากรู้ว่ามีเครื่องมืออะไรบ้าง