1. Cơ chế Hooks
Cơ chế Hooks
là một phương pháp để thêm logic tùy chỉnh trước hoặc sau các thay đổi cụ thể xảy ra trong các hoạt động cơ sở dữ liệu. Khi sửa đổi cấu trúc cơ sở dữ liệu, như thêm các nút mới, xóa các cạnh giữa các nút, hoặc xóa nhiều nút, chúng ta có thể sử dụng Hooks
để thực hiện xác nhận dữ liệu, ghi log, kiểm tra quyền hạn, hoặc bất kỳ hoạt động tùy chỉnh nào. Điều này rất quan trọng để đảm bảo tính nhất quán của dữ liệu và tuân thủ các quy tắc kinh doanh, đồng thời cho phép các nhà phát triển thêm chức năng bổ sung mà không cần thay đổi logic kinh doanh ban đầu.
2. Phương Pháp Đăng Ký Hook
2.1 Global Hooks và Local Hooks
Global hooks (Hooks thời gian chạy
) có hiệu lực đối với tất cả các loại hoạt động trong đồ thị. Chúng phù hợp để thêm logic vào toàn bộ ứng dụng, như ghi log và theo dõi. Local hooks (Hooks schema
) được xác định trong các schema loại cụ thể và chỉ áp dụng cho các hoạt động biến đổi phù hợp với loại schema. Sử dụng local hooks cho phép tất cả logic liên quan đến các loại nút cụ thể được tập trung tại một nơi, cụ thể là trong định nghĩa schema.
2.2 Bước Đăng Ký Hooks
Đăng ký một hook trong mã thường bao gồm các bước sau:
- Định nghĩa hàm hook. Hàm này nhận một
ent.Mutator
và trả về mộtent.Mutator
. Ví dụ, tạo một hook ghi log đơn giản:
logHook := func(next ent.Mutator) ent.Mutator {
return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
// In log trước hoạt động biến đổi
log.Printf("Trước khi biến đổi: Type=%s, Operation=%s\n", m.Type(), m.Op())
// Thực hiện hoạt động biến đổi
v, err := next.Mutate(ctx, m)
// In log sau hoạt động biến đổi
log.Printf("Sau khi biến đổi: Type=%s, Operation=%s\n", m.Type(), m.Op())
return v, err
})
}
- Đăng ký hook với client. Đối với các global hooks, bạn có thể đăng ký chúng bằng cách sử dụng phương thức
Use
của client. Đối với local hooks, bạn có thể đăng ký chúng trong schema bằng cách sử dụng phương thứcHooks
của loại.
// Đăng ký một global hook
client.Use(logHook)
// Đăng ký một local hook, chỉ áp dụng cho loại User
client.User.Use(func(next ent.Mutator) ent.Mutator {
return hook.UserFunc(func(ctx context.Context, m *ent.UserMutation) (ent.Value, error) {
// Thêm logic cụ thể
// ...
return next.Mutate(ctx, m)
})
})
- Bạn có thể chuỗi nhiều hooks và chúng sẽ được thực thi theo thứ tự đăng ký.
3. Thứ Tự Thực Thi Của Hooks
Thứ tự thực thi của hooks được xác định bởi thứ tự chúng được đăng ký với client. Ví dụ, client.Use(f, g, h)
sẽ thực thi trên hoạt động biến đổi theo thứ tự f(g(h(...)))
. Trong ví dụ này, f
là hook đầu tiên được thực thi, tiếp theo là g
, và cuối cùng là h
.
Quan trọng lưu ý rằng runtime hooks (Hooks thời gian chạy
) có ưu tiên hơn schema hooks (Hooks schema
). Điều này có nghĩa là nếu g
và h
là hooks được xác định trong schema trong khi f
được đăng ký bằng cách sử dụng client.Use(...)
, thứ tự thực thi sẽ là f(g(h(...)))
. Điều này đảm bảo rằng logic toàn cục, như ghi log, được thực thi trước tất cả các hooks khác.
4. Xử Lý Vấn Đề Do Hooks Gây Ra
Khi tùy chỉnh các hoạt động cơ sở dữ liệu bằng cách sử dụng Hooks, chúng ta có thể gặp vấn đề vòng lặp nhập. Điều này thường xảy ra khi cố gắng sử dụng schema hooks, do gói ent/schema
có thể giới thiệu gói cốt lõi ent
. Nếu gói cốt lõi ent
cũng cố gắng nhập gói ent/schema
, một sự phụ thuộc vòng lặp sẽ được hình thành.
Nguyên Nhân của Sự Phụ Thuộc Vòng Lặp
Sự phụ thuộc vòng lặp thường phát sinh từ sự phụ thuộc hai chiều giữa định nghĩa schema và mã thực thể được tạo ra. Điều này có nghĩa là ent/schema
phụ thuộc vào ent
(vì nó cần sử dụng các loại được cung cấp bởi framework ent
), trong khi mã được tạo ra bởi ent
cũng phụ thuộc vào ent/schema
(vì nó cần truy cập thông tin schema được định nghĩa bên trong).
Xử lý Vòng Lặp Phụ Thuộc
Nếu bạn gặp lỗi vòng lặp phụ thuộc, bạn có thể thực hiện các bước sau:
- Đầu tiên, tắt tất cả các hooks được sử dụng trong
ent/schema
. - Tiếp theo, di chuyển các loại tùy chỉnh được định nghĩa trong
ent/schema
sang một gói mới, ví dụ, bạn có thể tạo một gói có tênent/schema/schematype
. - Chạy lệnh
go generate ./...
để cập nhật góient
, sao cho nó trỏ vào đường dẫn gói mới, cập nhật các tham chiếu loại trong schema. Ví dụ, thay đổischema.T
thànhschematype.T
. - Bỏ comment những tham chiếu hooks đã tắt trước đó và chạy lại lệnh
go generate ./...
. Tại thời điểm này, quá trình tạo mã nên tiến hành mà không gây ra lỗi.
Bằng cách thực hiện những bước này, chúng ta có thể giải quyết vấn đề vòng lặp phụ thuộc gây ra bởi việc nhập hooks, đảm bảo logic của schema và việc thực hiện của Hooks có thể tiến hành một cách suôn sẻ.
5. Sử Dụng Các Hàm Hỗ Trợ Hook
Framework ent
cung cấp một tập hợp các hàm hỗ trợ hook, giúp chúng ta kiểm soát thời gian thực thi của Hooks. Dưới đây là một số ví dụ về các hàm hỗ trợ Hook thường được sử dụng:
// Chỉ thực thi HookA cho các hoạt động UpdateOne và DeleteOne
hook.On(HookA(), ent.OpUpdateOne|ent.OpDeleteOne)
// Không thực thi HookB trong quá trình tạo
hook.Unless(HookB(), ent.OpCreate)
// Thực thi HookC chỉ khi Mutation đang thay đổi trường "status" và xóa trường "dirty"
hook.If(HookC(), hook.And(hook.HasFields("status"), hook.HasClearedFields("dirty")))
// Cấm chỉnh sửa trường "password" trong các hoạt động cập nhật (nhiều)
hook.If(
hook.FixedError(errors.New("Không thể chỉnh sửa mật khẩu trong cập nhật nhiều")),
hook.And(
hook.HasOp(ent.OpUpdate),
hook.Or(
hook.HasFields("password"),
hook.HasClearedFields("password"),
),
),
)
Những hàm hỗ trợ này cho phép chúng ta kiểm soát chính xác các điều kiện kích hoạt của Hooks cho các hoạt động khác nhau.
6. Transaction Hooks
Transaction Hooks cho phép các Hook cụ thể được thực thi khi một giao dịch được commit (Tx.Commit
) hoặc rollback (Tx.Rollback
). Điều này rất hữu ích để đảm bảo tính nhất quán dữ liệu và tính nguyên tử của các hoạt động.
Ví dụ về Transaction Hooks
client.Tx(ctx, func(tx *ent.Tx) error {
// Đăng ký một transaction hook - hookBeforeCommit s