ข้อมูลเป็นส่วนสำคัญของแอปพลิเคชันซอฟต์แวร์ส่วนใหญ่ การทำแผนที่และการสืบค้นข้อมูลจากฐานข้อมูลเป็นงานที่เกิดซ้ำในชีวิตของนักพัฒนา ด้วยเหตุนี้ จึงเป็นสิ่งสำคัญที่จะต้องเข้าใจกระบวนการและสามารถใช้ abstractions ที่ทำให้งานง่ายขึ้นได้
ในโพสต์นี้ คุณจะพบการเปรียบเทียบระหว่าง ActiveRecord (Ruby) และ Ecto (Elixir) ซึ่งเป็นชุดแรกจากสองชุดข้อมูล เราจะมาดูกันว่าเครื่องมือทั้งสองช่วยให้นักพัฒนาย้ายข้อมูลและจับคู่สคีมาฐานข้อมูลได้อย่างไร
เราจะเปรียบเทียบแอปเปิ้ลกับส้ม (ต้นฉบับ) แบตเกิร์ลที่ไม่ต้องพูดอะไรเลย กับแบทแมน โดยระบุอย่างชัดเจนว่า 'ฉันคือแบทแมน' โดยนัย ข้อตกลงเกี่ยวกับการกำหนดค่า เทียบกับความตั้งใจที่ชัดเจน รอบที่หนึ่ง. สู้!
ActiveRecord
เป็นเวลากว่า 10 ปีนับตั้งแต่เปิดตัว คุณคงเคยได้ยินเกี่ยวกับ ActiveRecord ซึ่งเป็น ORM ที่มีชื่อเสียงซึ่งจัดส่งโดยค่าเริ่มต้นด้วยโปรเจ็กต์ Ruby on Rails
ActiveRecord คือ M ใน MVC - โมเดล - ซึ่งเป็นเลเยอร์ของระบบที่รับผิดชอบในการแสดงข้อมูลทางธุรกิจและตรรกะ ActiveRecord อำนวยความสะดวกในการสร้างและใช้งานอ็อบเจ็กต์ธุรกิจที่ข้อมูลต้องการการจัดเก็บข้อมูลแบบถาวรในฐานข้อมูล เป็นการนำรูปแบบ ActiveRecord ไปใช้งาน ซึ่งเป็นคำอธิบายของระบบ Object Relational Mapping
แม้ว่าส่วนใหญ่จะรู้จักกับ Rails แต่ ActiveRecord ก็สามารถใช้เป็นเครื่องมือแบบสแตนด์อโลนได้ โดยจะฝังอยู่ในโปรเจ็กต์อื่นๆ
Ecto
เมื่อเปรียบเทียบกับ ActiveRecord แล้ว Ecto เป็นเครื่องมือที่ค่อนข้างใหม่ (และในขณะนี้ยังไม่มีชื่อเสียง) เขียนด้วย Elixir และรวมอยู่ในโปรเจ็กต์ Phoenix โดยค่าเริ่มต้น
Ecto ไม่ใช่ ORM ต่างจาก ActiveRecord แต่เป็นไลบรารีที่ช่วยให้การใช้ Elixir เขียนข้อความค้นหาและโต้ตอบกับฐานข้อมูลได้
Ecto เป็นภาษาเฉพาะโดเมนสำหรับเขียนข้อความค้นหาและโต้ตอบกับฐานข้อมูลใน Elixir
จากการออกแบบ Ecto เป็นเครื่องมือแบบสแตนด์อโลน ซึ่งใช้ในโปรเจ็กต์ Elixir ต่างๆ และไม่ได้เชื่อมต่อกับเฟรมเวิร์กใดๆ
คุณเปรียบเทียบแอปเปิ้ลกับส้มไม่ใช่หรือ
ใช่! แม้ว่า ActiveRecord และ Ecto จะมีความหมายแตกต่างกัน แต่คุณลักษณะทั่วไป เช่น การโยกย้ายฐานข้อมูล การแมปฐานข้อมูล การสืบค้น และการตรวจสอบ ได้รับการสนับสนุนโดยทั้ง ActiveRecord และ Ecto และเราสามารถบรรลุผลลัพธ์เดียวกันได้โดยใช้เครื่องมือทั้งสอง สำหรับผู้ที่สนใจ Elixir ที่มีพื้นฐานมาจาก Ruby เราคิดว่านี่น่าจะเป็นการเปรียบเทียบที่น่าสนใจ
ระบบใบแจ้งหนี้
ตลอดช่วงที่เหลือของโพสต์ ระบบจะใช้ระบบใบแจ้งหนี้สมมติเพื่อสาธิต ลองนึกภาพว่าเรามีร้านขายชุดให้เหล่าซุปเปอร์ฮีโร่ เพื่อให้ง่ายขึ้น เราจะมีเพียงสองตารางสำหรับระบบใบแจ้งหนี้:ผู้ใช้ และ ใบแจ้งหนี้ .
ด้านล่างนี้คือโครงสร้างของตารางเหล่านั้น พร้อมด้วยฟิลด์และประเภท:
ผู้ใช้
ฟิลด์ | พิมพ์ |
---|---|
ชื่อเต็ม | สตริง |
อีเมล | สตริง |
created_at (ActiveRecord) / inserted_at (Ecto) | วันที่และเวลา |
updated_at | วันที่และเวลา |
ใบแจ้งหนี้
ฟิลด์ | พิมพ์ |
---|---|
user_id | จำนวนเต็ม |
payment_method | สตริง |
paid_at | วันที่และเวลา |
created_at (ActiveRecord) / inserted_at (Ecto) | วันที่และเวลา |
updated_at | วันที่และเวลา |
ตารางผู้ใช้มีสี่ช่อง:full_name , อีเมล , updated_at และช่องที่สี่ขึ้นอยู่กับเครื่องมือที่ใช้ ActiveRecord สร้าง created_at ฟิลด์ในขณะที่ Ecto สร้าง inserted_at เพื่อแสดงการประทับเวลาของช่วงเวลาที่บันทึกครั้งแรกในฐานข้อมูล
ตารางที่ 2 ชื่อ ใบแจ้งหนี้ . มีห้าช่อง:user_id , payment_method , paid_at , updated_at และคล้ายกับตารางผู้ใช้ created_at หรือ inserted_at ขึ้นอยู่กับเครื่องมือที่ใช้
ตารางผู้ใช้และใบแจ้งหนี้มีการเชื่อมโยงดังต่อไปนี้:
- ผู้ใช้รายหนึ่งมีใบแจ้งหนี้จำนวนมาก
- ใบแจ้งหนี้เป็นของผู้ใช้
การย้ายถิ่น
การย้ายข้อมูลช่วยให้นักพัฒนาสามารถพัฒนาสคีมาฐานข้อมูลของตนได้อย่างง่ายดายเมื่อเวลาผ่านไป โดยใช้กระบวนการวนซ้ำ ทั้ง ActiveRecord และ Ecto ช่วยให้นักพัฒนาสามารถโยกย้ายสคีมาฐานข้อมูลโดยใช้ภาษาระดับสูง (Ruby และ Elixir ตามลำดับ) แทนที่จะจัดการกับ SQL โดยตรง
มาดูกันว่าการย้ายข้อมูลทำงานอย่างไรใน ActiveRecord และ Ecto โดยใช้พวกมันเพื่อสร้างตารางผู้ใช้และใบแจ้งหนี้
ActiveRecord:การสร้างตารางผู้ใช้
การย้ายถิ่น
class CreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
t.string :full_name, null: false
t.string :email, index: {unique: true}, null: false
t.timestamps
end
end
end
การโยกย้าย ActiveRecord ช่วยให้สามารถสร้างตารางโดยใช้ create_table
กระบวนการ. แม้ว่า created_at
และ updated_at
ไม่ได้กำหนดฟิลด์ในไฟล์การโยกย้าย การใช้ t.timestamps
ทริกเกอร์ ActiveRecord เพื่อสร้างทั้งสองอย่าง
สร้างโครงสร้างตาราง
หลังจากรัน CreateUsers
การย้ายข้อมูล ตารางที่สร้างขึ้นจะมีโครงสร้างดังนี้:
Column | Type | Nullable | Default
------------+-----------------------------+----------+-----------------------------------
id | bigint | not null | nextval('users_id_seq'::regclass)
full_name | character varying | not null |
email | character varying | not null |
created_at | timestamp without time zone | not null |
updated_at | timestamp without time zone | not null |
Indexes:
"users_pkey" PRIMARY KEY, btree (id)
"index_users_on_email" UNIQUE, btree (email)
การย้ายข้อมูลยังรับผิดชอบในการสร้างดัชนีเฉพาะสำหรับฟิลด์อีเมล ตัวเลือก index: {unique: true}
ถูกส่งไปยังข้อกำหนดฟิลด์อีเมล นี่คือสาเหตุที่ตารางแสดงรายการ "index_users_on_email" UNIQUE, btree (email)
ดัชนีเป็นส่วนหนึ่งของโครงสร้าง
Ecto:การสร้างตารางผู้ใช้
การย้ายถิ่น
defmodule Financex.Repo.Migrations.CreateUsers do
use Ecto.Migration
def change do
create table(:users) do
add :full_name, :string, null: false
add :email, :string, null: false
timestamps()
end
create index(:users, [:email], unique: true)
end
end
การโยกย้าย Ecto รวมฟังก์ชัน create()
และ table()
เพื่อสร้างตารางผู้ใช้ ไฟล์การโยกย้าย Ecto ค่อนข้างคล้ายกับ ActiveRecord ที่เทียบเท่า ใน ActiveRecord ฟิลด์การประทับเวลา (created_at
และ updated_at
) ถูกสร้างขึ้นโดย t.timestamps
ในขณะที่อยู่ใน Ecto ฟิลด์การประทับเวลา (inserted_at
และ updated_at
) ถูกสร้างขึ้นโดย timestamps()
ฟังก์ชัน
เครื่องมือทั้งสองมีความแตกต่างกันเล็กน้อยเกี่ยวกับวิธีการสร้างดัชนี ใน ActiveRecord ดัชนีถูกกำหนดเป็นตัวเลือกสำหรับฟิลด์ที่กำลังสร้าง Ecto ใช้การรวมฟังก์ชัน create()
และ index()
เพื่อให้บรรลุเป้าหมายนั้น สอดคล้องกับวิธีการใช้ชุดค่าผสมเพื่อสร้างตารางเอง
สร้างโครงสร้างตาราง
Column | Type | Nullable | Default
-------------+-----------------------------+----------+-----------------------------------
id | bigint | not null | nextval('users_id_seq'::regclass)
full_name | character varying(255) | not null |
email | character varying(255) | not null |
inserted_at | timestamp without time zone | not null |
updated_at | timestamp without time zone | not null |
Indexes:
"users_pkey" PRIMARY KEY, btree (id)
"users_email_index" UNIQUE, btree (email)
ตารางที่สร้างจากการรัน Financex.Repo.Migrations.CreateUsers
การย้ายข้อมูลมีโครงสร้างเหมือนกันกับตารางที่สร้างโดยใช้ ActiveRecord
ActiveRecord:การสร้าง invoices
ตาราง
การย้ายถิ่น
class CreateInvoices < ActiveRecord::Migration[5.2]
def change
create_table :invoices do |t|
t.references :user
t.string :payment_method
t.datetime :paid_at
t.timestamps
end
end
end
การย้ายข้อมูลนี้รวมถึง t.references
วิธีที่ไม่มีอยู่ในวิธีก่อนหน้า ใช้เพื่อสร้างการอ้างอิงไปยังตารางผู้ใช้ ตามที่อธิบายไว้ก่อนหน้านี้ ผู้ใช้มีใบแจ้งหนี้จำนวนมากและใบแจ้งหนี้เป็นของผู้ใช้ t.references
เมธอดสร้าง user_id
ในตารางใบแจ้งหนี้เพื่อใช้อ้างอิง
สร้างโครงสร้างตาราง
Column | Type | Nullable | Default
----------------+-----------------------------+----------+--------------------------------------
id | bigint | not null | nextval('invoices_id_seq'::regclass)
user_id | bigint | |
payment_method | character varying | |
paid_at | timestamp without time zone | |
created_at | timestamp without time zone | not null |
updated_at | timestamp without time zone | not null |
Indexes:
"invoices_pkey" PRIMARY KEY, btree (id)
"index_invoices_on_user_id" btree (user_id)
ตารางที่สร้างขึ้นจะมีรูปแบบเดียวกับตารางที่สร้างไว้ก่อนหน้านี้ ข้อแตกต่างเพียงอย่างเดียวคือดัชนีพิเศษ (index_invoices_on_user_id
) ซึ่ง ActiveRecord จะเพิ่มโดยอัตโนมัติเมื่อ t.references
ใช้วิธีการ
Ecto:การสร้าง invoices
ตาราง
การย้ายถิ่น
defmodule Financex.Repo.Migrations.CreateInvoices do
use Ecto.Migration
def change do
create table(:invoices) do
add :user_id, references(:users)
add :payment_method, :string
add :paid_at, :utc_datetime
timestamps()
end
create index(:invoices, [:user_id])
end
end
Ecto ยังสนับสนุนการสร้างการอ้างอิงฐานข้อมูล โดยใช้ references()
การทำงาน. ซึ่งแตกต่างจาก ActiveRecord ซึ่งอนุมานชื่อคอลัมน์ Ecto ต้องการให้นักพัฒนากำหนด user_id
อย่างชัดเจน ชื่อคอลัมน์ references()
ฟังก์ชันยังต้องให้นักพัฒนากำหนดตารางที่อ้างอิงถึงอย่างชัดเจน ซึ่งในตัวอย่างนี้คือตารางผู้ใช้
สร้างโครงสร้างตาราง
Column | Type | Nullable | Default
----------------+-----------------------------+----------+--------------------------------------
id | bigint | not null | nextval('invoices_id_seq'::regclass)
user_id | bigint | |
payment_method | character varying(255) | |
paid_at | timestamp without time zone | |
inserted_at | timestamp without time zone | not null |
updated_at | timestamp without time zone | not null |
Indexes:
"invoices_pkey" PRIMARY KEY, btree (id)
"invoices_user_id_index" btree (user_id)
Foreign-key constraints:
"invoices_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id)
การโยกย้ายทั้งสองยังค่อนข้างคล้ายกัน เมื่อพูดถึง references
มีการจัดการคุณลักษณะ มีความแตกต่างเล็กน้อย:
-
Ecto สร้างข้อ จำกัด ของคีย์ต่างประเทศให้กับ
user_id
ฟิลด์ ("invoices_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id)
) ซึ่งรักษาความสมบูรณ์ของการอ้างอิงระหว่างผู้ใช้และตารางใบแจ้งหนี้ -
ActiveRecord สร้างดัชนีโดยอัตโนมัติสำหรับ
user_id
คอลัมน์. Ecto ต้องการให้นักพัฒนามีความชัดเจนในเรื่องนี้ นี่คือสาเหตุที่การย้ายข้อมูลมีcreate index(:invoices, [:user_id])
คำชี้แจง
ActiveRecord:การแมปข้อมูลและการเชื่อมโยง
ActiveRecord เป็นที่รู้จักสำหรับคำขวัญ "การประชุมผ่านการกำหนดค่า" มันอนุมานชื่อตารางฐานข้อมูลโดยใช้ชื่อคลาสโมเดล โดยดีฟอลต์ คลาสชื่อ User
โดยค่าเริ่มต้น จะใช้ users
ตารางเป็นแหล่งที่มา ActiveRecord ยังจับคู่คอลัมน์ทั้งหมดของตารางเป็นแอตทริบิวต์ของอินสแตนซ์ นักพัฒนาจำเป็นต้องกำหนดความสัมพันธ์ระหว่างตารางเท่านั้น ActiveRecord ยังใช้สิ่งเหล่านี้เพื่อสรุปคลาสและตารางที่เกี่ยวข้อง
ดูวิธีการจับคู่ผู้ใช้และตารางใบแจ้งหนี้โดยใช้ ActiveRecord:
ผู้ใช้
class User < ApplicationRecord
has_many :invoices
end
ใบแจ้งหนี้
class Invoice < ApplicationRecord
belongs_to :user
end
Ecto:การแมปข้อมูลและการเชื่อมโยง
ในทางกลับกัน Ecto ต้องการให้นักพัฒนามีความชัดเจนเกี่ยวกับแหล่งข้อมูลและฟิลด์ต่างๆ แม้ว่า Ecto จะมี has_many
. ที่คล้ายกัน และ belongs_to
นอกจากนี้ ยังต้องการให้นักพัฒนามีความชัดเจนเกี่ยวกับตารางที่เกี่ยวข้องและโมดูลสคีมาที่ใช้ในการจัดการกับสคีมาของตารางนั้นด้วย
นี่คือวิธีที่ Ecto จับคู่ผู้ใช้และตารางใบแจ้งหนี้:
ผู้ใช้
defmodule Financex.Accounts.User do
use Ecto.Schema
schema "users" do
field :full_name, :string
field :email, :string
has_many :invoices, Financex.Accounts.Invoice
timestamps()
end
end
ใบแจ้งหนี้
defmodule Financex.Accounts.Invoice do
use Ecto.Schema
schema "invoices" do
field :payment_method, :string
field :paid_at, :utc_datetime
belongs_to :user, Financex.Accounts.User
timestamps()
end
end
สรุป
ในโพสต์นี้ เราเปรียบเทียบแอปเปิ้ลกับส้มโดยไม่กะพริบตา เราเปรียบเทียบวิธีที่ ActiveRecord และ Ecto จัดการกับการย้ายฐานข้อมูลและการทำแผนที่ การต่อสู้ของแบตเกิร์ลดั้งเดิมโดยปริยายกับแบทแมน 'I'm Batman' ที่ชัดเจน
ขอบคุณ "การประชุมผ่านการกำหนดค่า" การใช้ ActiveRecord มักจะเกี่ยวข้องกับการเขียนน้อยลง Ecto ไปในทิศทางตรงกันข้าม ทำให้นักพัฒนาต้องมีความชัดเจนมากขึ้นเกี่ยวกับเจตนาของพวกเขา นอกเหนือจาก "โค้ดที่น้อยกว่า" โดยทั่วไปแล้ว ActiveRecord มีค่าเริ่มต้นที่เหมาะสมซึ่งช่วยนักพัฒนาไม่ต้องตัดสินใจในทุกสิ่งและยังต้องเข้าใจการกำหนดค่าพื้นฐานทั้งหมด สำหรับผู้เริ่มต้น ActiveRecord เป็นโซลูชันที่เหมาะสมกว่า เพราะมันทำให้การตัดสินใจ "ดีพอ" เป็นค่าเริ่มต้น ตราบใดที่คุณปฏิบัติตามมาตรฐานอย่างเคร่งครัด
ลักษณะที่ชัดเจนของ Ecto ช่วยให้อ่านและทำความเข้าใจพฤติกรรมของโค้ดได้ง่ายขึ้น แต่ยังต้องการให้นักพัฒนาเข้าใจเพิ่มเติมเกี่ยวกับคุณสมบัติของฐานข้อมูลและคุณลักษณะที่พร้อมใช้งาน สิ่งที่ทำให้ Ecto ดูยุ่งยากในแวบแรกคือหนึ่งในข้อดีของมัน จากประสบการณ์ส่วนตัวของฉันทั้งในโลกของ ActiveRecord และ Ecto ความชัดเจนของ Ecto จะลบเอฟเฟกต์ "เบื้องหลัง" และความไม่แน่นอนซึ่งพบได้ทั่วไปในโปรเจ็กต์ที่มี ActiveRecord สิ่งที่นักพัฒนาซอฟต์แวร์อ่านในโค้ดคือสิ่งที่เกิดขึ้นในแอปพลิเคชันและไม่มีพฤติกรรมโดยนัย
ในบล็อกที่สองในอีกไม่กี่สัปดาห์ ในชุดสองส่วน "ActiveRecord vs Ecto" เราจะอธิบายวิธีการทำงานของคิวรีและการตรวจสอบความถูกต้องทั้งใน ActiveRecord และ Ecto
เราชอบที่จะรู้ว่าคุณคิดอย่างไรกับบทความนี้ เรามองหาหัวข้อใหม่ๆ อยู่เสมอ ดังนั้นหากคุณมีหัวข้อที่ต้องการเรียนรู้เพิ่มเติม โปรดอย่าลังเลที่จะแจ้งให้เราทราบที่ @AppSignal!