Dữ liệu là một phần cốt lõi của hầu hết các ứng dụng phần mềm. Ánh xạ và truy vấn dữ liệu từ cơ sở dữ liệu là một công việc lặp đi lặp lại trong cuộc đời của một nhà phát triển. Do đó, điều quan trọng là phải hiểu quy trình và có thể sử dụng các nội dung trừu tượng để đơn giản hóa tác vụ.
Trong bài đăng này, bài đầu tiên trong số hai bài, bạn sẽ tìm thấy sự so sánh giữa ActiveRecord (Ruby) và Ecto (Elixir). Chúng ta sẽ xem cách cả hai công cụ cho phép các nhà phát triển di chuyển và lập bản đồ các lược đồ cơ sở dữ liệu.
Vì vậy, chúng tôi sẽ so sánh Táo và Cam. (Bản gốc) Batgirl, người không bao giờ cần nói một lời, so với Batman, tuyên bố rõ ràng 'Tôi là Người dơi'. Ẩn ý, quy ước về cấu hình, so với Ý định rõ ràng. Vòng một. Chiến đấu!
ActiveRecord
Với hơn 10 năm kể từ khi phát hành, rất có thể bạn đã nghe nói về ActiveRecord - ORM nổi tiếng được vận chuyển theo mặc định với các dự án Ruby on Rails.
ActiveRecord là M trong MVC - mô hình - là lớp của hệ thống chịu trách nhiệm đại diện cho dữ liệu kinh doanh và logic. ActiveRecord tạo điều kiện thuận lợi cho việc tạo và sử dụng các đối tượng nghiệp vụ có dữ liệu yêu cầu lưu trữ liên tục trong cơ sở dữ liệu. Nó là một triển khai của mẫu ActiveRecord, chính nó là một mô tả của hệ thống Ánh xạ quan hệ đối tượng.
Mặc dù chủ yếu được biết đến là được sử dụng với Rails, ActiveRecord cũng có thể được sử dụng như một công cụ độc lập, được nhúng vào các dự án khác.
Ecto
Khi so sánh với ActiveRecord, Ecto là một công cụ khá mới (và ở thời điểm hiện tại chưa nổi tiếng). Nó được viết bằng Elixir và được đưa vào mặc định trong các dự án Phoenix.
Không giống như ActiveRecord, Ecto không phải là ORM mà là một thư viện cho phép sử dụng Elixir để viết các truy vấn và tương tác với cơ sở dữ liệu.
Ecto là ngôn ngữ dành riêng cho miền để viết truy vấn và tương tác với cơ sở dữ liệu trong Elixir.
Theo thiết kế, Ecto là một công cụ độc lập, được sử dụng trong các dự án Elixir khác nhau và không được kết nối với bất kỳ khuôn khổ nào.
Không phải bạn đang so sánh Táo và Cam sao?
Vâng chúng tôi! Mặc dù ActiveRecord và Ecto khác nhau về ngữ nghĩa, nhưng các tính năng chung như di chuyển cơ sở dữ liệu, ánh xạ cơ sở dữ liệu, truy vấn và xác nhận đều được hỗ trợ bởi cả ActiveRecord và Ecto. Và chúng ta có thể đạt được kết quả tương tự khi sử dụng cả hai công cụ. Đối với những người quan tâm đến Elixir đến từ nền tảng Ruby, chúng tôi nghĩ đây sẽ là một so sánh thú vị.
Hệ thống hóa đơn
Trong suốt phần còn lại của bài đăng, một hệ thống hóa đơn giả định sẽ được sử dụng để chứng minh. Hãy tưởng tượng chúng ta có một cửa hàng bán bộ đồ cho các siêu anh hùng. Để đơn giản hóa mọi thứ, chúng tôi sẽ chỉ có hai bảng cho hệ thống hóa đơn: người dùng và hóa đơn .
Dưới đây là cấu trúc của các bảng đó, với các trường và kiểu của chúng:
người dùng
Field | Loại |
---|---|
full_name | chuỗi |
chuỗi | |
create_at (ActiveRecord) / insert_at (Ecto) | ngày giờ |
updated_at | ngày giờ |
hóa đơn
Field | Loại |
---|---|
user_id | số nguyên |
Payment_method | chuỗi |
pay_at | ngày giờ |
create_at (ActiveRecord) / insert_at (Ecto) | ngày giờ |
updated_at | ngày giờ |
Bảng người dùng có bốn trường: full_name , email , updated_at và trường thứ tư phụ thuộc vào công cụ được sử dụng. ActiveRecord tạo một create_at trong khi Ecto tạo một insert_at trường đại diện cho dấu thời gian của thời điểm bản ghi được chèn lần đầu tiên vào cơ sở dữ liệu.
Bảng thứ hai có tên là hóa đơn . Nó có năm trường: user_id , Payment_method , pay_at , updated_at và, tương tự như bảng người dùng, create_at hoặc insert_at , tùy thuộc vào công cụ được sử dụng.
Người dùng và bảng hóa đơn có các liên kết sau:
- Một người dùng có nhiều hóa đơn
- Hóa đơn thuộc về người dùng
Di chuyển
Quá trình di chuyển cho phép các nhà phát triển dễ dàng phát triển lược đồ cơ sở dữ liệu của họ theo thời gian, sử dụng một quy trình lặp đi lặp lại. Cả ActiveRecord và Ecto đều cho phép các nhà phát triển di chuyển lược đồ cơ sở dữ liệu bằng cách sử dụng ngôn ngữ cấp cao (tương ứng là Ruby và Elixir), thay vì xử lý trực tiếp với SQL.
Hãy xem cách di chuyển hoạt động trong ActiveRecord và Ecto bằng cách sử dụng chúng để tạo người dùng và bảng hóa đơn.
ActiveRecord:Tạo Bảng Người dùng
Di chuyển
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
Di chuyển ActiveRecord cho phép tạo bảng bằng create_table
phương pháp. Mặc dù created_at
và updated_at
các trường không được xác định trong tệp di chuyển, việc sử dụng t.timestamps
kích hoạt ActiveRecord để tạo cả hai.
Cấu trúc bảng đã tạo
Sau khi chạy CreateUsers
khi di chuyển, bảng đã tạo sẽ có cấu trúc sau:
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)
Việc di chuyển cũng chịu trách nhiệm về việc tạo ra một chỉ mục duy nhất cho trường email. Tùy chọn index: {unique: true}
được chuyển đến định nghĩa trường email. Đây là lý do tại sao bảng liệt kê "index_users_on_email" UNIQUE, btree (email)
chỉ mục như một phần của cấu trúc của nó.
Ecto:Tạo Bảng Người dùng
Di chuyển
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
Di chuyển Ecto kết hợp các hàm create()
và table()
để tạo bảng người dùng. Tệp di chuyển Ecto khá giống với tệp tương đương ActiveRecord của nó. Trong ActiveRecord các trường dấu thời gian (created_at
và updated_at
) được tạo bởi t.timestamps
trong khi ở Ecto các trường dấu thời gian (inserted_at
và updated_at
) được tạo bởi timestamps()
chức năng.
Có một sự khác biệt nhỏ giữa cả hai công cụ về cách tạo chỉ mục. Trong ActiveRecord, chỉ mục được định nghĩa là một tùy chọn cho trường đang được tạo. Ecto sử dụng kết hợp các hàm create()
và index()
để đạt được điều đó, nhất quán với cách kết hợp được sử dụng để tạo bảng.
Cấu trúc bảng đã tạo
Cột 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)
Bảng được tạo khi chạy Financex.Repo.Migrations.CreateUsers
chuyển đổi có cấu trúc giống với bảng được tạo bằng ActiveRecord.
ActiveRecord:Tạo invoices
Bảng
Di chuyển
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
Việc di chuyển này bao gồm t.references
phương pháp đó không có trong phương pháp trước đó. Nó được sử dụng để tạo tham chiếu đến bảng người dùng. Như đã mô tả trước đó, một người dùng có nhiều hóa đơn và một hóa đơn thuộc về một người dùng. t.references
phương thức tạo một user_id
trong bảng hóa đơn để chứa tham chiếu đó.
Cấu trúc bảng đã tạo
Cột 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)
Bảng đã tạo tuân theo các mẫu tương tự như bảng đã tạo trước đó. Sự khác biệt duy nhất là một chỉ mục phụ (index_invoices_on_user_id
), ActiveRecord tự động thêm vào khi t.references
phương pháp được sử dụng.
Ecto:Tạo invoices
Bảng
Di chuyển
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 cũng hỗ trợ tạo các tham chiếu cơ sở dữ liệu, bằng cách sử dụng references()
hàm số. Không giống như ActiveRecord, với tên cột, Ecto yêu cầu nhà phát triển xác định rõ ràng user_id
tên cột dọc. Tham chiếu references()
hàm cũng yêu cầu nhà phát triển xác định rõ ràng bảng mà tham chiếu đang trỏ đến, trong ví dụ này, là bảng người dùng.
Cấu trúc bảng đã tạo
Cột 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)
Cả hai cuộc di cư cũng khá giống nhau. Khi nói đến cách references
tính năng được xử lý, có một số khác biệt:
-
Ecto tạo ràng buộc khóa ngoại cho
user_id
trường ("invoices_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id)
), duy trì tính toàn vẹn tham chiếu giữa người dùng và các bảng hóa đơn. -
ActiveRecord tự động tạo chỉ mục cho
user_id
cột. Ecto yêu cầu nhà phát triển phải rõ ràng về điều đó. Đây là lý do tại sao quá trình di chuyển cócreate index(:invoices, [:user_id])
tuyên bố.
ActiveRecord:Bản đồ và liên kết dữ liệu
ActiveRecord được biết đến với phương châm "quy ước trên cấu hình". Nó suy ra tên bảng cơ sở dữ liệu bằng cách sử dụng tên lớp mô hình, theo mặc định. Một lớp có tên User
, theo mặc định, sử dụng users
bảng làm nguồn của nó. ActiveRecord cũng ánh xạ tất cả các cột của bảng dưới dạng một thuộc tính cá thể. Các nhà phát triển chỉ được yêu cầu xác định các liên kết giữa các bảng. Chúng cũng được ActiveRecord sử dụng để suy ra các lớp và bảng có liên quan.
Hãy xem cách người dùng và bảng hóa đơn được ánh xạ bằng ActiveRecord:
người dùng
Người dùngclass User < ApplicationRecord
has_many :invoices
end
hóa đơn
Hóa đơnclass Invoice < ApplicationRecord
belongs_to :user
end
Ecto:Lập bản đồ và liên kết dữ liệu
Mặt khác, Ecto yêu cầu nhà phát triển phải rõ ràng về nguồn dữ liệu và các trường của nó. Mặc dù Ecto có has_many
tương tự và belongs_to
, nó cũng yêu cầu các nhà phát triển phải rõ ràng về bảng được liên kết và mô-đun lược đồ được sử dụng để xử lý lược đồ bảng đó.
Đây là cách Ecto lập bản đồ người dùng và bảng hóa đơn:
người dùng
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
hóa đơn
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
Kết thúc
Trong bài đăng này, chúng tôi đã so sánh táo và cam không chớp mắt. Chúng tôi đã so sánh cách ActiveRecord và Ecto xử lý việc lập bản đồ và di chuyển cơ sở dữ liệu. Một trận chiến giữa Batgirl gốc đầy ẩn ý và người dơi 'Tôi là Người dơi' rõ ràng.
Nhờ "quy ước trên cấu hình", sử dụng ActiveRecord thường ít phải viết hơn. Ecto đi theo hướng ngược lại, yêu cầu các nhà phát triển phải rõ ràng hơn về ý định của họ. Ngoài việc "ít mã hơn" nói chung tốt hơn, ActiveRecord có một số mặc định tối ưu để giúp nhà phát triển không phải đưa ra quyết định về mọi thứ và cũng phải hiểu tất cả các cấu hình cơ bản. Đối với người mới bắt đầu, ActiveRecord là một giải pháp phù hợp hơn, bởi vì nó đưa ra các quyết định "đủ tốt" theo mặc định, miễn là bạn tuân thủ nghiêm ngặt tiêu chuẩn của nó.
Khía cạnh rõ ràng của Ecto làm cho việc đọc và hiểu hành vi của một đoạn mã trở nên dễ dàng hơn, nhưng nó cũng yêu cầu nhà phát triển hiểu thêm về các thuộc tính cơ sở dữ liệu và các tính năng có sẵn. Điều có thể làm cho Ecto trông cồng kềnh ngay từ cái nhìn đầu tiên, là một trong những ưu điểm của nó. Dựa trên kinh nghiệm cá nhân của tôi trong cả thế giới ActiveRecord và Ecto, tính rõ ràng của Ecto loại bỏ các hiệu ứng "phía sau hậu trường" và sự không chắc chắn thường gặp trong các dự án với ActiveRecord. Những gì nhà phát triển đọc trong mã, là những gì xảy ra trong ứng dụng và không có hành vi ngầm nào.
Trong blog thứ hai sau vài tuần, trong loạt bài "ActiveRecord vs Ecto" hai phần, chúng tôi sẽ trình bày cách thức hoạt động của các truy vấn và xác thực trong cả ActiveRecord và Ecto.
Chúng tôi rất muốn biết bạn nghĩ gì về bài viết này. Chúng tôi luôn theo dõi các chủ đề mới để đề cập, vì vậy nếu bạn có chủ đề muốn tìm hiểu thêm, vui lòng cho chúng tôi biết tại @AppSignal!