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:

  1. Mendefinisikan fungsi hook. Fungsi ini mengambil ent.Mutator dan mengembalikan ent.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
    })
}
  1. Mendaftarkan hook dengan klien. Untuk hook global, Anda dapat mendaftarkannya menggunakan metode Use pada klien. Untuk hook lokal, Anda dapat mendaftarkannya dalam skema menggunakan metode Hooks 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)
    })
})
  1. 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:

  1. Pertama, komentari semua hooks yang digunakan dalam ent/schema.
  2. Selanjutnya, pindahkan tipe-tipe kustom yang didefinisikan di ent/schema ke paket baru, misalnya, Anda dapat membuat paket bernama ent/schema/schematype.
  3. Jalankan perintah go generate ./... untuk memperbarui paket ent, sehingga menunjuk ke jalur paket baru, memperbarui referensi tipe dalam skema. Misalnya, ubah schema.T menjadi schematype.T.
  4. 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.