Transactions in Rails

Transactions in Rails

I. Giới thiệu tổng quan

Làm việc với một ngôn ngữ lập trình web chắc bạn không còn lạ lẫm với các công việc tương tác với CSDL, việc tạo mới, sửa hay lưu trữ các bản ghi..Và việc xử lý các trường hợp xảy ra lỗi khi thực hiện ghi dữ liệu vào các bảng trong cơ sở dữ liệu là việc không thể thiếu trong khi viết ra các xử lý dòng dữ liệu cho các ứng dụng web. Vậy xử lý transactions là xử lý như thế nào? Nó chính là việc trả lại nguyên vẹn dữ liệu khi có lỗi xảy ra, tránh trường hợp thiếu đồng nhất giữa hai bên.

II. Lý do thực hiện transactions

Chúng ta sử dụng transactions như là một wrapper bảo vệ các câu lệnh SQL để đảm bảo những thay đổi trong cơ sở dữ liệu chỉ xảy ra khi tất cả các hành động thành công với nhau. Transactions giúp các lập trình thực thi toàn vẹn dữ liệu trong ứng dụng. Ví dụ kinh điển của giao dịch là phương pháp ngân hàng nơi tiền được rút từ một tài khoản và gửi vào tiếp theo. Nếu một trong các bước sau thất bại, sau đó toàn bộ quá trình sẽ thiết lập lại. Ví dụ này có thể được mô tả trong code theo cách này:

0.PNG

Trong Rails transaction có sẵn phương thức instance hay phương thức của lớp cho tất cả các ActiveRecord. Điều này có nghĩa là một trong hai cách trên đều giá trị như nhau:

1.PNG

Bạn cũng có thể nhận thấy rằng có những lớp mô hình khác nhau được tham chiếu trong transactions này. Nó là hoàn hảo để kết hợp các loại mô hình bên trong một khối giao dịch duy nhất. Điều này là do transactions được liên kết với các kết nối cơ sở dữ liệu không phải là mô hình ví dụ. Theo quy định, transactions chỉ cần thiết khi thay đổi cho nhiều bản ghi phải thành công như một đơn vị. Ngoài ra, Rails đã kết gói gọn các phương pháp #save và #destroy trong một giao dịch, do đó một transaction là không bao giờ cần thiết khi cập nhật một bản ghi duy nhất.

III. Transaction Rollback Triggers

Transactions thiết lập lại tình trạng của các bản ghi thông qua một quá trình được gọi là một rollback. Trong Rails, rollbacks chỉ được kích hoạt bởi một ngoại lệ. Đây là một điểm rất quan trọng để hiểu được; Tôi thấy một số khối giao dịch đó sẽ không bao giờ rollback vì mã có chứa không thể ném ra một ngoại lệ. Ở đây tôi có một chút biến đổi ví dụ ngân hàng:

2.PNG

Trong Rails #update_attribute được thiết kế không phải để ném một ngoại lệ khi một bản ghi cập nhật lỗi. Nó trả về false, và vì lý do này, bạn nên đảm bảo rằng các phương pháp sử dụng ném một ngoại lệ khi thất bại. Một cách tốt hơn để viết các ví dụ trên sẽ là:

3.PNG

Lưu ý: (!) là một quy ước trong rails cho một phương pháp mà sẽ ném một ngoại lệ khi thất bại.

Chúng ta cũng thấy các ví dụ trong đoạn code nơi mà các bản ghi đã được tìm thấy bên trong transactions sử dụng tìm kiếm bản ghi bằng #find_by. Theo thiết hàm tìm kiếm của rails sẽ trả về nil khi không có bản ghi được trả về. Điều này trái ngược với phương pháp #find_ bình thường mà ném một ngoại lệ ActiveRecord :: RecordNotFound. Hãy xem xét ví dụ này:

4.PNG

Bạn có thấy lỗi logic? Các đối tượng rống sẽ có một thuộc tính id và nó sẽ che dấu một thực tế rằng các bản ghi mong muốn không được trả lại. Trong khi điều này không gây ra một lỗi trong transaction vẫn sẽ dẫn đến mất tính toàn vẹn dữ liệu, bởi vì các ứng dụng được cập nhật mà nó không nên. Nhớ rằng một transaction định nghĩa một khối mã đó phải thành công như là một đơn vị nguyên tử. Điều này có nghĩa chúng ta phải bắn ra một ngoại lệ khi một phụ thuộc logic như thế này không được đáp ứng. Dưới đây là cách nên viết:

5.PNG

Khi ngoại lệ xảy ra nó sẽ được đưa ra bên ngoài của khối transaction sau khi rollback đã hoàn thành. Đoạn code trong ứng dụng của bạn phải sẵn sàng để bắt ngoại lệ này ngay khi nó được nổi lên qua stack của ứng dụng.

Nó cũng có thể làm mất hiệu lực một transaction mà không cần bắn ra một ngoại lệ đó sẽ truyền lên bằng cách sử dụng ActiveRecord :: Rollback. Ngoại lệ đặc biệt này cho phép bạn làm mất hiệu lực một transaction và thiết lập lại các bản ghi cơ sở dữ liệu mà không cần phải xử lý những nơi khác trong mã của bạn.

IV. Khi nào nên sử dụng Nested Transactions

Một trong những sai lầm phổ biến thường thấy trong codebase là sử dụng sai hoặc lạm dụng các giao dịch lồng nhau. Khi bạn lồng một transaction bên trong một transaction khác, điều này có ảnh hưởng tới transactions con. Điều này có thể có những kết quả đáng ngạc nhiên, đưa ví dụ này từ tài liệu API Rails:

6.PNG

Như đã đề cập trước đó ActiveRecord :: Rollback không đưa ra bên ngoài của khối transaction và vì vậy các transaction cha không nhận được ngoại lệ lồng vào bên trong các transaction con. Từ các nội dung của transaction con được gộp vào các giao dịch cha cùng khi các bản ghi được tạo ra! Chúng ta cảm thấy nó dễ dàng hơn để nghĩ về các giao dịch lồng nhau.

Để đảm bảo một rollback nhận được transactions cha, bạn phải thêm: requires_new => true. tùy chọn để transaction con. Một lần nữa bằng cách sử dụng ví dụ từ nguồn Rails bạn sẽ kích hoạt Rollback của cha như sau:

7.PNG

Một transaction này chỉ hoạt động khi kết nối cơ sở dữ liệu hiện hành. Nếu ứng dụng của bạn viết cho nhiều cơ sở dữ liệu cùng một lúc bạn sẽ cần để bọc các phương pháp bên trong một giao dịch lồng nhau. Ví dụ:

8.PNG

V. Callback trong Transactions

Như đã đề cập trước đó #save và #destroy được gói bên trong một transaction. Điều này có nghĩa rằng callbacks như #after_save vẫn là một phần của transaction hoạt động mà vẫn có thể được rollback! Vì vậy nếu bạn muốn code để thực thi được đảm bảo để thực hiện bên ngoài của transaction sử dụng một trong các callbacks transaction cụ thể như #after_commit hoặc #after_rollback.

VI. Transaction Gotchas

Không bắt một ngoại lệ ActiveRecord :: RecordInvalid bên trong một transaction vì các ngoại lệ này làm mất hiệu lực của transaction trên một số cơ sở dữ liệu như Postgres. Một khi các transaction không hợp lệ, bạn phải khởi động lại nó từ đầu để nó hoạt động một cách chính xác.

Khi kiểm tra rollbacks hoặc callbacks transaction liên quan xảy ra sau rollback bạn sẽ muốn tắt transactional_fixtures mà là mặc định trong hầu hết các test framework.

VII. Một số partterns nên tránh khi viết ứng dụng

  1. Sử dụng transaction khi chỉ có 1 bản ghi được cập nhật
  2. Những nested transaction không cần thiết
  3. Transaction chứa đoạn code không gây ra một rollback
  4. Sử dụng transaction trong 1 controller.

Bài viết được tham khảo tại: Markdaggett.

Rất mong các bạn ủng hộ!