Xử lý Transaction trong Rails

I. Tổng quan

Transaction là một thuật ngữ chung trong thiết kế phần mềm, nó được hiểu như một tiến trình xử lý có xác định điểm đầu và điểm cuối, được chia nhỏ thành các operation (phép thực thi).
Tiến trình được thực thi một cách tuần tự và độc lập các operation đó theo nguyên tắc hoặc tất cả đều thành công hoặc một operation thất bại thì toàn bộ tiến trình thất bại. Nếu việc thực thi một operation nào đó bị fail đồng nghĩa với việc dữ liệu phải rollback lại trạng thái ban đầu.
Trong rails, transaction giúp dữ liệu trong database được toàn vẹn, tức là các thay đổi trong cơ sở dữ liệu chỉ được giữ lại khi tất cả các câu lệnh SQL trong transaction đều được thực hiện thành công.

II. Cách sử dụng

Để sử dụng transaction trong rails có nhiều cách:

  • Call by ActiveRecord::Base

    ActiveRecord::Base.transaction do
       # code
    end
    
  • Call by Class

    Account.transaction do
        # code
    end
    
  • Call by instance

    account.transaction do
       # code
    end
    

Nhưng thông thường Call by ActiveRecord::Base được sử dụng phổ biến hơn cả Có một lưu ý là object bên trong transaction không nhất thiết phải được tạo từ class gọi nó. Ta xét ví dụ sau:

Account.transaction do
  balance.save!
  account.save!
end

Như ta thấy đối tượng balance bên trong transaction không phải là instance của Account mặc dù transaction được gọi bởi class Account. Đó là bởi vì transaction được thực hiện trên kết nối database chứ ko phải trên model

III. Kích hoạt rollback và xử lý exception

Trong rails, transaction thành công nếu có commit được thực hiện, thất bại nếu có bất kỳ exception nào được raise lên.
Exception sẽ tự động kích hoạt rollback dữ liệu về lại trạng thái trước khi bắt đầu transaction.

def self.transfer_handle_ex_1 user1, user2
  ActiveRecord::Base.transaction do
    user1.update!(money: user1.money + 100)
    user2.update!(money: user2.money1 - 100)
  end
end

money1 ở ví dụ trên không tồn tại nên exception NoMethodError sẽ được raise và kích hoạt rollback



Các exception sau khi kích hoạt rollback cũng sẽ được ném ra ngoài nên chúng ta cần phải xử lý các exception đó, chẳng hạn như in ra một đoạn thông báo thân thiện với người dùng.

def self.transfer_handle_ex_1 user1, user2
  ActiveRecord::Base.transaction do
    user1.update!(money: user1.money + 100)
    user2.update!(money: user2.money1 - 100)
  end
rescue NoMethodError
  puts "Opp! Has errors"
end

**Lưu ý:**
  • Vì rollback được kích hoạt bởi exception nên để đảm bảo tính đúng đắn của transaction, bên trong nó ta nên sử dụng các method có raise exception nếu fail như: create!, update!, save! ...
  • savedestroy tự động được gói lại trong 1 transaction.

Ngoài việc rollback được kích hoạt tự động bởi exception ta cũng có thể kích hoạt thủ công bằng cách raise ActiveRecord::Rollback.

def self.transfer user1, user2
  ActiveRecord::Base.transaction do
    user1.update!(money: user1.money + 100)
    user2.update!(money: user2.money - 100)
    raise ActiveRecord::Rollback if user2.money < 1500
  end
end

Ở đây, ActiveRecord::Rollback là một exception đặc biệt, nó chỉ có nhiệm vụ kích hoạt rollback mà không được ném ra ngoài.

IV. Transaction lồng nhau (nested transaction)

Trong Rail transaction có thể lồng nhau, nhưng bất kỳ exception nào (kể cả ActiveRecord::Rollback) bên trong sub-transaction cũng không thể kích hoạt rollback. Để có thể kích hoạt rollback trong sub-transaction ta cần thêm tùy chọn requires_new: true vào sub-transaction đó.

def self.nested_transaction user1, user2
  ActiveRecord::Base.transaction do
    user1.update!(money: user1.money + 100)
    
    ActiveRecord::Base.transaction(requires_new: true) do
      user2.update!(money: user2.money - 100)
      raise ActiveRecord::Rollback
    end
  end
end

Kết quả sẽ chỉ có money của user1 được update. Tuy nhiên chỉ có một số database hỗ trợ nested transaction.

V. Callback

Có hai loại callbacks liên quan đến commit và rollback trong transaction: after_commitafter_rollback.

  • after_commit được gọi tới ngay sau khi transaction thực hiện thành công.
  • after_rollback được gọi tới ngay sau khi transaction bị rollback.

VI. Tài liệu tham khảo