1. Mekanisme Hooks
Mekanisme Hooks
adalah metode untuk menambahkan logika kustom sebelum atau sesudah perubahan tertentu terjadi dalam operasi database. Ketika memodifikasi skema database, seperti menambahkan node baru, menghapus edge antara node, atau menghapus beberapa node, kita dapat menggunakan Hooks
untuk melakukan validasi data, pencatatan log, pemeriksaan izin, atau operasi kustom apapun. Ini sangat penting untuk memastikan konsistensi data dan kepatuhan terhadap aturan bisnis, sambil juga memungkinkan pengembang untuk menambahkan fungsionalitas tambahan tanpa mengubah logika bisnis asli.
2. Metode Pendaftaran Hooks
2.1 Hooks Global dan Hooks Lokal
Hooks global (Hooks runtime
) efektif untuk semua jenis operasi dalam grafik. Mereka cocok untuk menambahkan logika ke seluruh aplikasi, seperti pencatatan dan pemantauan. Hooks lokal (Hooks skema
) didefinisikan dalam skema jenis tertentu dan hanya berlaku untuk operasi mutasi yang cocok dengan jenis skema. Menggunakan hooks lokal memungkinkan semua logika terkait jenis node tertentu untuk terpusat di satu tempat, yaitu dalam definisi skema.
2.2 Langkah-langkah Pendaftaran Hooks
Pendaftaran hook dalam kode biasanya melibatkan langkah-langkah berikut:
- Mendefinisikan fungsi hook. Fungsi ini mengambil
ent.Mutator
dan mengembalikanent.Mutator
. Misalnya, membuat hook pencatatan sederhana:
logHook := func(next ent.Mutator) ent.Mutator {
return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
// Cetak log sebelum operasi mutasi
log.Printf("Sebelum memutasi: Tipe=%s, Operasi=%s\n", m.Type(), m.Op())
// Melakukan operasi mutasi
v, err := next.Mutate(ctx, m)
// Cetak log setelah operasi mutasi
log.Printf("Setelah memutasi: Tipe=%s, Operasi=%s\n", m.Type(), m.Op())
return v, err
})
}
- Mendaftarkan hook dengan klien. Untuk hook global, Anda dapat mendaftarkannya menggunakan metode
Use
pada klien. Untuk hook lokal, Anda dapat mendaftarkannya dalam skema menggunakan metodeHooks
pada jenisnya.
// Mendaftarkan hook global
client.Use(logHook)
// Mendaftarkan hook lokal, hanya berlaku untuk jenis Pengguna
client.User.Use(func(next ent.Mutator) ent.Mutator {
return hook.UserFunc(func(ctx context.Context, m *ent.UserMutation) (ent.Value, error) {
// Tambahkan logika spesifik
// ...
return next.Mutate(ctx, m)
})
})
- Anda dapat menyusun beberapa hook, dan mereka akan dieksekusi sesuai urutan pendaftarannya.
3. Urutan Eksekusi Hooks
Urutan eksekusi hooks ditentukan oleh urutan pendaftarannya dengan klien. Misalnya, client.Use(f, g, h)
akan dieksekusi pada operasi mutasi dengan urutan f(g(h(...)))
. Dalam contoh ini, f
adalah hook pertama yang dieksekusi, diikuti oleh g
, dan terakhir h
.
Perlu dicatat bahwa hooks runtime (Hooks runtime
) memiliki prioritas di atas hooks skema (Hooks skema
). Ini berarti jika g
dan h
adalah hooks yang didefinisikan dalam skema sedangkan f
didaftarkan menggunakan client.Use(...)
, urutan eksekusinya akan menjadi f(g(h(...)))
. Hal ini memastikan bahwa logika global, seperti pencatatan, dieksekusi sebelum semua hook lainnya.
4. Penanganan Masalah yang Disebabkan oleh Hooks
Ketika menyesuaikan operasi database menggunakan Hooks, kita mungkin menghadapi isu siklus impor. Ini biasanya terjadi saat mencoba menggunakan hooks skema, karena paket ent/schema
dapat memperkenalkan paket inti ent
. Jika paket inti ent
juga mencoba mengimpor ent/schema
, maka akan terbentuk dependensi lingkaran.
Penyebab Dependensi Lingkaran
Dependensi lingkaran biasanya muncul dari dependensi dua arah antara definisi skema dan kode entitas yang dihasilkan. Ini berarti bahwa ent/schema
bergantung pada ent
(karena perlu menggunakan tipe yang disediakan oleh kerangka kerja ent
), sedangkan kode yang dihasilkan oleh ent
juga bergantung pada ent/schema
(karena perlu mengakses informasi skema yang didefinisikan di dalamnya).
Penyelesaian Ketergantungan Lingkaran
Jika Anda mengalami kesalahan ketergantungan lingkaran, Anda dapat mengikuti langkah-langkah berikut:
- Pertama, komentari semua hooks yang digunakan dalam
ent/schema
. - Selanjutnya, pindahkan tipe-tipe kustom yang didefinisikan di
ent/schema
ke paket baru, misalnya, Anda dapat membuat paket bernamaent/schema/schematype
. - Jalankan perintah
go generate ./...
untuk memperbarui paketent
, sehingga menunjuk ke jalur paket baru, memperbarui referensi tipe dalam skema. Misalnya, ubahschema.T
menjadischematype.T
. - Hapus komentar dari referensi hooks yang sebelumnya dikomentari dan jalankan perintah
go generate ./...
lagi. Pada titik ini, generasi kode harus berjalan tanpa kesalahan.
Dengan mengikuti langkah-langkah ini, kita dapat menyelesaikan masalah ketergantungan lingkaran yang disebabkan oleh impor Hooks, memastikan bahwa logika skema dan implementasi Hooks dapat berjalan lancar.
5. Penggunaan Fungsi Bantu Hook
Framework ent
menyediakan serangkaian fungsi bantu hook, yang dapat membantu kita mengontrol timing eksekusi Hooks. Berikut adalah beberapa contoh fungsi bantu Hook yang umum digunakan:
// Hanya menjalankan HookA untuk operasi UpdateOne dan DeleteOne
hook.On(HookA(), ent.OpUpdateOne|ent.OpDeleteOne)
// Jangan jalankan HookB selama operasi Create
hook.Unless(HookB(), ent.OpCreate)
// Jalankan HookC hanya ketika Mutasi mengubah kolom "status" dan menghapus kolom "dirty"
hook.If(HookC(), hook.And(hook.HasFields("status"), hook.HasClearedFields("dirty")))
// Larang mengubah kolom "password" dalam operasi Update (banyak)
hook.If(
hook.FixedError(errors.New("password tidak dapat diedit pada banyak pembaruan")),
hook.And(
hook.HasOp(ent.OpUpdate),
hook.Or(
hook.HasFields("password"),
hook.HasClearedFields("password"),
),
),
)
Fungsi-fungsi bantu ini memungkinkan kita untuk mengontrol kondisi aktivasi Hooks dengan tepat untuk operasi yang berbeda.
6. Hook Transaksi
Hook Transaksi memungkinkan Hooks spesifik untuk dieksekusi ketika transaksi di-commit (Tx.Commit
) atau di-rollback (Tx.Rollback
). Ini sangat berguna untuk memastikan konsistensi data dan atomisitas operasi.
Contoh Hook Transaksi
client.Tx(ctx, func(tx *ent.Tx) error {
// Mendaftarkan hook transaksi - hookBeforeCommit akan dieksekusi sebelum commit.
tx.OnCommit(func(next ent.Committer) ent.Committer {
return ent.CommitFunc(func(ctx context.Context, tx *ent.Tx) error {
// Logika sebelum commit sebenarnya dapat diletakkan di sini.
fmt.Println("Sebelum commit")
return next.Commit(ctx, tx)
})
})
// Lakukan serangkaian operasi dalam transaksi...
return nil
})
Kode di atas menunjukkan cara mendaftarkan hook transaksi untuk dijalankan sebelum commit dalam transaksi. Hook ini akan dipanggil setelah semua operasi database dieksekusi dan sebelum transaksi benar-benar di-commit.