Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

ข้อดีและข้อเสียของการใช้ structure.sql ใน Ruby on Rails Application ของคุณ

ในโพสต์ของวันนี้ เราจะพูดถึงความแตกต่างที่สำคัญและประโยชน์ของการใช้ structure.sql เทียบกับ schema.rb default ที่เป็นค่าเริ่มต้น รูปแบบสคีมาในแอปพลิเคชัน Ruby on Rails ของคุณ ในโลกที่ขับเคลื่อนด้วยข้อมูล การรู้วิธีใช้ประโยชน์จากฟีเจอร์ที่หลากหลายของฐานข้อมูลสามารถสร้างความแตกต่างระหว่างองค์กรที่ประสบความสำเร็จและไม่ประสบความสำเร็จได้

หลังจากที่เห็นความแตกต่างหลักๆ ระหว่างทั้งสองรูปแบบแล้ว เราจะสรุปวิธีการเปลี่ยนไปใช้ structure.sql และสาธิตวิธีการช่วยให้มั่นใจถึงความสมบูรณ์ของข้อมูลรวมถึงฟังก์ชันของฐานข้อมูลที่คุณอาจเก็บรักษาไว้ไม่ได้

ในโพสต์ ผมจะยกตัวอย่างแอป Rails ที่ใช้ structure.sql ด้วยฐานข้อมูล PostgreSQL แต่แนวคิดพื้นฐานสามารถย้ายไปยังฐานข้อมูลอื่นได้เช่นกัน ไม่มีเว็บแอปพลิเคชันในโลกแห่งความเป็นจริงที่สมบูรณ์อย่างแท้จริงหากไม่มีฐานข้อมูลที่เชื่อถือได้รองรับ

เพื่อไม่ให้เป็นการเสียเวลา มาดำน้ำกัน!

ความแตกต่างระหว่าง schema.rb และ structure.sql

สิ่งแรกที่คุณต้องทำเมื่อเริ่มต้นโปรเจ็กต์ Ruby on Rails คือการเรียกใช้การย้ายฐานข้อมูล ตัวอย่างเช่น หากคุณสร้างโมเดลผู้ใช้ Rails จะขอให้คุณเรียกใช้การย้ายข้อมูลอย่างหลีกเลี่ยงไม่ได้ ซึ่งจะสร้าง schema.rb ไฟล์ตามลำดับ:

rails g model User first_name:string last_name:string

Rails จะสร้างการโยกย้ายต่อไปนี้:

class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      t.string :first_name
      t.string :last_name
 
      t.timestamps
    end
  end
end

เมื่อดำเนินการย้ายข้อมูล คุณจะพบว่า Rails สร้าง schema.rb ไฟล์สำหรับคุณ:

ActiveRecord::Schema.define(version: 2019_12_14_074018) do
 
  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"
 
  create_table "users", force: :cascade do |t|
    t.string "first_name"
    t.string "last_name"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end
 
end

schema.rbนี้ เป็นไฟล์ที่ยอดเยี่ยมสำหรับแอปพลิเคชันและกรณีการใช้งานที่ค่อนข้างพื้นฐาน

มีสองสิ่งสำคัญที่ควรสังเกตที่นี่:

  1. เป็นตัวแทน Ruby ของฐานข้อมูลของคุณ schema.rb ถูกสร้างขึ้นโดยการตรวจสอบฐานข้อมูลและแสดงโครงสร้างโดยใช้ Ruby
  2. เป็นฐานข้อมูลที่ไม่เชื่อเรื่องพระเจ้า (เช่น คุณใช้ SQLite, PostgreSQL, MySQL หรือฐานข้อมูลอื่นใดที่ Rails รองรับ ไวยากรณ์และโครงสร้างส่วนใหญ่จะยังคงเหมือนเดิม)

อย่างไรก็ตาม อาจมีบางครั้งที่กลยุทธ์นี้จำกัดเกินไปสำหรับแอปที่กำลังเติบโตของคุณ

ตัวอย่างเช่น คุณมีไฟล์การย้ายข้อมูลนับร้อยหรือหลายพันไฟล์

หากคุณต้องการสร้างระบบการผลิตใหม่อย่างรวดเร็ว คุณอาจพบสถานการณ์ที่การเรียกใช้งานทั้งหมดตามลำดับใช้เวลานานเกินไป หรือคุณอาจเผชิญกับสถานการณ์ที่การย้ายข้อมูลบางส่วนมีโค้ดที่ตั้งใจให้เรียกใช้บนฐานข้อมูลเวอร์ชันเก่าของคุณ แต่ไม่สามารถดำเนินการได้อีกต่อไปในเวอร์ชันปัจจุบัน คุณอาจมีสถานการณ์ที่การย้ายข้อมูลถูกเขียนขึ้นโดยมีการสันนิษฐานว่าข้อมูลบางอย่างใช้ไม่ได้อีกต่อไป ซึ่งจะทำให้การย้ายข้อมูลล้มเหลว

สถานการณ์ทั้งหมดเหล่านี้ป้องกันการตั้งค่าอินสแตนซ์ใหม่ของแอปพลิเคชันของคุณอย่างมีประสิทธิภาพ——ไม่ว่าจะเป็นในเวอร์ชันที่ใช้งานจริงหรือสำหรับสมาชิกในทีมใหม่——ด้วย rails db:create db:migrate ที่เรียบง่าย สั่งการ. หากเป็นกรณีนี้ คุณจะดำเนินการตามสคีมาฐานข้อมูลที่ถูกต้องได้อย่างไร

แน่นอน วิธีหนึ่งคือการกลับไปแก้ไขการโยกย้ายที่เสียหายทั้งหมด นั่นไม่ใช่ความคิดที่เลวเลย!

หากการกลับไปแก้ไขการย้ายข้อมูลจำนวนมากมีค่าใช้จ่ายสูงเกินไป อีกวิธีหนึ่งก็คือการเรียกใช้ rails db:setup งาน. งานนี้จะสร้างสคีมาฐานข้อมูลจาก schema.rb . ของคุณ ไฟล์. อย่างไรก็ตาม จะเกิดอะไรขึ้นหากฐานข้อมูลของคุณมีตรรกะที่ซับซ้อนซึ่งไม่ได้แสดงใน schema.rb เป็นตัวแทนของฐานข้อมูลของคุณหรือไม่

โชคดีที่ Rails มีทางเลือกอื่น:structure.sql

structure.sql แตกต่างจาก schema.rb ด้วยวิธีดังต่อไปนี้:

  • อนุญาตให้คัดลอกโครงสร้างฐานข้อมูลได้อย่างแม่นยำ นี่เป็นสิ่งสำคัญเมื่อทำงานกับทีม เช่นเดียวกับถ้าคุณต้องการสร้างฐานข้อมูลใหม่ในการผลิตจาก rails db:setup อย่างรวดเร็ว งาน
  • ช่วยให้สามารถรักษาข้อมูลคุณลักษณะฐานข้อมูลขั้นสูงได้ ตัวอย่างเช่น หากคุณใช้ PostgreSQL จะทำให้สามารถใช้มุมมอง มุมมองที่เป็นรูปธรรม ฟังก์ชัน ข้อจำกัด และอื่นๆ

เมื่อแอปพลิเคชันถึงระดับที่กำหนด เราต้องใช้ทุกเคล็ดลับในหนังสือเพื่อเพิ่มประสิทธิภาพ รักษาความถูกต้องของข้อมูล และรับประกันประสิทธิภาพการทำงานที่รวดเร็ว การใช้ structure.sql เพื่อจัดการพฤติกรรมของฐานข้อมูล Rails ให้ผู้ใช้สามารถทำเช่นนั้นได้

การเปลี่ยนจาก schema.rb ไปยัง structure.sql

ทำการเปลี่ยนแปลงจาก schema.rb ไปยัง structure.sql เป็นกระบวนการที่ค่อนข้างตรงไปตรงมา สิ่งที่คุณต้องทำคือตั้งค่าบรรทัดต่อไปนี้ใน config/application.rb :

module YourApp
  class Application < Rails::Application
    config.load_defaults 6.0
 
    # Add this line:
    config.active_record.schema_format = :sql
  end
end

จากนั้นเรียกใช้ rails db:migrate และคุณควรเห็นไฟล์ใน db/structure.sql . โว้ว! Rails จะดัมพ์โครงสร้างฐานข้อมูลโดยใช้เครื่องมือเฉพาะสำหรับฐานข้อมูลที่คุณใช้ (ในกรณีของ PostgreSQL เครื่องมือนั้นคือ pg_dump สำหรับ MySQL หรือ MariaDB จะมีผลลัพธ์ของ SHOW CREATE TABLE สำหรับแต่ละโต๊ะ ฯลฯ) ขอแนะนำให้ตรวจสอบให้แน่ใจว่าไฟล์นี้อยู่ภายใต้การควบคุมเวอร์ชัน เพื่อให้ทีมที่เหลือของคุณมีโครงสร้างฐานข้อมูลเดียวกัน

แวบแรกที่เห็นไฟล์นั้นอาจดูน่ากลัว:schema.rb ไฟล์มีเพียง 25 บรรทัด ในขณะที่ structure.sql ไฟล์มหันต์ 109 บรรทัด! ไฟล์ขนาดใหญ่เช่นนี้มีประโยชน์อย่างไรต่อการพัฒนาแอป

การเพิ่มข้อจำกัดระดับฐานข้อมูล

ActiveRecord เป็นหนึ่งในส่วนที่ฉันโปรดปรานในการใช้ Rails ช่วยให้คุณสามารถสืบค้นฐานข้อมูลในลักษณะที่เป็นธรรมชาติ เกือบจะเหมือนกับในภาษาพูด ตัวอย่างเช่น หากคุณต้องการค้นหาผู้ใช้ของบริษัททั้งหมดที่ชื่อ Dan ActiveRecord จะให้คุณเรียกใช้แบบสอบถามได้ง่ายๆ ดังนี้:

company = Company.find(name: 'Some Company')
 
# Reads just like in a natural language!
company.users.where(first_name: 'Dan')

มีบางกรณีที่ ActiveRecord สั้นแม้ว่า ตัวอย่างเช่น สมมติว่าคุณมีการตรวจสอบความถูกต้องต่อไปนี้ในโมเดลผู้ใช้ของคุณ:

class User < ApplicationRecord
  validate :name_cannot_start_with_d
 
  private
 
  def name_cannot_start_with_d
    if first_name.present? && first_name[0].downcase == 'd'
      errors.add(:first_name, "cannot start with the letter 'D'")
    end
  end
end

หากคุณพยายามสร้างผู้ใช้ชื่อ 'แดน' คุณควรเห็นข้อผิดพลาดเมื่อทำการตรวจสอบความถูกต้อง:

User.create!(first_name: 'Dan')
Traceback (most recent call last):
ActiveRecord::RecordInvalid (Validation failed: First name cannot start with the letter 'D')

เป็นเรื่องปกติ แต่สมมติว่าคุณหรือสมาชิกในทีมของคุณเปลี่ยนข้อมูลโดยข้ามการตรวจสอบความถูกต้องของ ActiveRecord:

u = User.create(first_name: 'Pan')
 
# The update_attribute method bypasses ActiveRecord validations
u.update_attribute :first_name, 'Dan'
u.first_name
=> "Dan"

ดังที่ได้แสดงให้เห็นแล้ว การเลี่ยงผ่านการตรวจสอบทำได้ง่ายมาก

สิ่งนี้สามารถส่งผลร้ายต่อการสมัครของเรา ActiveRecord สามารถให้พรเช่นเดียวกับคำสาป——ในขณะที่มี DSL ที่สะอาดและเป็นธรรมชาติมากที่ทำให้การทำงานด้วยมีความสุข แต่ก็มักจะอนุญาตมากเกินไปเมื่อบังคับใช้การตรวจสอบระดับแบบจำลอง วิธีแก้ปัญหาอย่างที่คุณอาจทราบแล้วคือการเพิ่มข้อจำกัดระดับฐานข้อมูล

rails g migration AddFirstNameConstraintToUser

สิ่งนี้จะสร้างไฟล์ที่คุณสามารถแก้ไขได้ด้วยตรรกะที่จะไม่อนุญาตให้ใช้ชื่อที่ขึ้นต้นด้วยตัวอักษร 'D':

class AddFirstNameConstraintToUser < ActiveRecord::Migration[6.0]
  def up
    execute "ALTER TABLE users ADD CONSTRAINT name_cannot_start_with_d CHECK (first_name !~* '^d')"
  end
 
  def down
    execute "ALTER TABLE users DROP CONSTRAINT IF EXISTS name_cannot_start_with_d"
  end
end

โปรดทราบว่า มาก สิ่งสำคัญคือต้องเพิ่มรหัสที่คืนค่าการโยกย้ายได้สำเร็จ ในตัวอย่างข้างต้น ฉันมี up และ down คำสั่ง up เมธอดจะถูกดำเนินการเมื่อมีการย้ายข้อมูล down ได้รับการดำเนินการเมื่อมีการย้อนกลับการย้ายข้อมูล หากไม่คืนค่าโครงสร้างฐานข้อมูลของคุณอย่างถูกต้อง คุณอาจต้องทำความสะอาดบ้านด้วยตนเองในภายหลัง ฉันขอแนะนำให้มีไฟล์การโยกย้ายที่สามารถดำเนินการได้ทั้ง up และ down เพื่อหลีกเลี่ยงอาการปวดหัวในอนาคต

ตอนนี้ เรียกใช้การย้ายข้อมูลและตรวจสอบว่าคุณสามารถข้ามข้อจำกัดนั้นได้หรือไม่:

rails db:migrate
user = User.create first_name: 'Pan'
user.update_attribute :first_name, 'Dan'
 
ActiveRecord::StatementInvalid (PG::CheckViolation: ERROR:  new row for relation "users" violates check constraint "name_cannot_start_with_d")
DETAIL:  Failing row contains (2, Dan, null, 2019-12-14 09:40:11.809358, 2019-12-14 09:40:41.658974).

สมบูรณ์แบบ! ข้อจำกัดของเราทำงานตามที่ตั้งใจไว้ แม้ว่าเราจะหลีกเลี่ยงการตรวจสอบความถูกต้องของ ActiveRecord ไม่ว่าด้วยเหตุผลใดก็ตาม เรายังคงสามารถพึ่งพาฐานข้อมูล——ผู้รักษาประตูขั้นสูงสุดของเรา⁠—เพื่อรักษาความสมบูรณ์ของข้อมูลของเรา

สิ่งนี้เกี่ยวข้องกับ structure.sql ?

หากคุณลองดู คุณจะพบว่ามีการเพิ่มสิ่งต่อไปนี้:

CREATE TABLE public.users (
    id bigint NOT NULL,
    first_name character varying,
    last_name character varying,
    created_at timestamp(6) without time zone NOT NULL,
    updated_at timestamp(6) without time zone NOT NULL,
    CONSTRAINT name_cannot_start_with_d CHECK (((first_name)::text !~* '^d'::text)));

ข้อจำกัดของคุณอยู่ในสคีมาเอง!

ขณะที่ schema.rb ยังรองรับข้อจำกัดระดับฐานข้อมูลด้วย สิ่งสำคัญคือต้องจำไว้ว่ามันไม่ได้แสดงทุกอย่างที่ฐานข้อมูลของคุณอาจสนับสนุน เช่น ทริกเกอร์ ลำดับ กระบวนงานที่เก็บไว้ หรือข้อจำกัดการตรวจสอบ ตัวอย่างเช่น นี่คือสิ่งที่จะเกิดขึ้นกับไฟล์สคีมาของคุณด้วยการย้ายข้อมูลแบบเดียวกัน (AddFirstNameConstraintToUser ) หากคุณเพียงแค่ใช้ schema.rb :

ActiveRecord::Schema.define(version: 2019_12_14_074018) do
 
  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"
 
  create_table "users", force: :cascade do |t|
    t.string "first_name"
    t.string "last_name"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end
 
end

ไฟล์ไม่เปลี่ยน! ไม่ได้เพิ่มข้อจำกัด

หากคุณต้องจ้างนักพัฒนารายใหม่มาทำงานในโครงการ คุณอาจอยู่ภายใต้ระเบียบข้อบังคับของฐานข้อมูลที่แตกต่างกัน

คอมมิต structure.sql การควบคุมเวอร์ชันจะช่วยให้แน่ใจว่าทีมของคุณอยู่ในหน้าเดียวกัน หากคุณต้องเรียกใช้ rails db:setup มี structure.sql โครงสร้างฐานข้อมูลของคุณจะมีข้อจำกัดข้างต้น ด้วย schema.rb ไม่มีการค้ำประกันดังกล่าว

สามารถพูดได้เช่นเดียวกันเกี่ยวกับระบบการผลิต หากคุณต้องการสร้างอินสแตนซ์ใหม่ของแอปพลิเคชันของคุณอย่างรวดเร็วด้วยฐานข้อมูลใหม่——และการเรียกใช้การย้ายข้อมูลทั้งหมดตามลำดับจะใช้เวลานาน——การตั้งค่าฐานข้อมูลจาก structure.sql ไฟล์จะเร็วกว่ามาก เราวางใจได้ว่า structure.sql จะสร้างฐานข้อมูลของเราด้วยโครงสร้างเดียวกันกับในกรณีอื่นๆ

ความเจ็บปวดที่เพิ่มขึ้น

การจัดการ schema.rb ที่กระชับ ไฟล์ทั่วทั้งทีมนั้นง่ายกว่าการจัดการ structure.sql อย่างละเอียด ไฟล์.

ปัญหาใหญ่ที่สุดอย่างหนึ่งเมื่อย้ายไปยัง structure.sql คือการสร้างความมั่นใจว่าเฉพาะการเปลี่ยนแปลงที่จำเป็นเท่านั้นที่จะถูกส่งไปยังไฟล์นั้น ซึ่งบางครั้งอาจทำได้ยาก

ตัวอย่างเช่น คุณดึงสาขาของใครบางคนและเรียกใช้การย้ายข้อมูลเฉพาะสำหรับสาขานั้น structure.sqlของคุณ ตอนนี้จะมีการเปลี่ยนแปลงบางอย่าง จากนั้นคุณกลับไปทำงานในสาขาของคุณเองและสร้างการย้ายข้อมูลใหม่ structure.sqlของคุณ ไฟล์จะมีทั้งการเปลี่ยนแปลงของสาขาและสาขาอื่น นี่อาจเป็นเรื่องยุ่งยากในการจัดการ และแน่นอนว่าต้องมีช่วงการเรียนรู้เล็กน้อยเมื่อต้องจัดการกับความขัดแย้งเหล่านี้

ด้วยการใช้แนวทางนี้ เรากำลังทำการประนีประนอม เราต้องจัดการกับความซับซ้อนของโค้ดล่วงหน้า ซึ่งช่วยให้เราสามารถรักษาฟังก์ชันการทำงานขั้นสูงของฐานข้อมูลของเราไว้ได้ ในทางกลับกัน เรายังต้องจัดการกับการแสดงสคีมาที่ง่ายกว่า รวมถึงการไม่มีพลังของฐานข้อมูลเพียงปลายนิ้วสัมผัส เช่น หากเราต้องการตั้งค่าการสำรองข้อมูลจาก db:setup งาน. ฉันคิดว่าควรอดทนต่อความยุ่งยากในการควบคุมเวอร์ชันสักเล็กน้อย ดีกว่าต้องทนกับการแก้ไขข้อมูลที่เสียหาย/ไม่ถูกต้องในระบบที่ใช้งานจริง หรือไม่สามารถใช้ฟังก์ชันขั้นสูงทั้งหมดที่ฐานข้อมูลของคุณนำเสนอได้

โดยทั่วไป มีสองกลยุทธ์ที่ฉันเคยใช้เพื่อให้แน่ใจว่า structure.sql file มีเพียงการเปลี่ยนแปลงที่จำเป็นต่อสาขาเฉพาะ:

  • เมื่อคุณทำงานในสาขาที่มีการโยกย้ายเสร็จแล้ว ตรวจสอบให้แน่ใจว่าคุณเรียกใช้ rails db:rollback STEP=n โดยที่ n คือจำนวนการย้ายถิ่นในสาขานั้นๆ เพื่อให้แน่ใจว่าโครงสร้างฐานข้อมูลของคุณจะเปลี่ยนกลับเป็นสถานะเดิม
  • คุณอาจลืมย้อนกลับหลังจากทำงานในสาขา ในกรณีนั้น เมื่อทำงานกับสาขาใหม่ ตรวจสอบให้แน่ใจว่าคุณได้ดึง structure.sql ที่เก่าแก่ จากมาสเตอร์ก่อนสร้างการย้ายข้อมูลใหม่

ตามหลักการทั่วไป structure.sql . ของคุณ ไฟล์ควรมีเฉพาะการเปลี่ยนแปลงที่เกี่ยวข้องกับสาขาของคุณก่อนที่จะรวมเข้ากับมาสเตอร์

บทสรุป

โดยทั่วไปแล้ว เมื่อแอปพลิเคชัน Rails มีขนาดเล็กหรือไม่ต้องการคุณลักษณะขั้นสูงบางอย่างที่ฐานข้อมูลนำเสนอ ก็สามารถใช้ schema.rb ได้อย่างปลอดภัย ซึ่งอ่านง่าย กระชับ และจัดการง่าย

อย่างไรก็ตาม เนื่องจากแอปพลิเคชันมีขนาดและความซับซ้อนเพิ่มขึ้น การสะท้อนโครงสร้างฐานข้อมูลที่แม่นยำจึงเป็นสิ่งสำคัญ จะช่วยให้ทีมสามารถรักษาข้อจำกัดที่เหมาะสม โมดูลฐานข้อมูล ฟังก์ชันและตัวดำเนินการที่ไม่เช่นนั้นจะไม่สามารถทำได้ เรียนรู้การใช้ Rails ด้วย structure.sql . ที่ได้รับการดูแลเป็นอย่างดี ไฟล์จะนำเสนอขอบที่ง่ายกว่า schema.rb ทำไม่ได้

ป.ล. หากคุณต้องการอ่านโพสต์ Ruby Magic ทันทีที่ออกจากสื่อ สมัครรับจดหมายข่าว Ruby Magic ของเราและไม่พลาดแม้แต่โพสต์เดียว!