1. Hooks Mekanizması
Hooks
mekanizması, veritabanı işlemlerinde belirli değişikliklerden önce veya sonra özel mantık eklemek için kullanılan bir yöntemdir. Veritabanı şemasını değiştirirken, yeni düğümler eklerken, düğümler arasındaki kenarları silerken veya birden fazla düğümü silerken, Hooks
kullanarak veri doğrulama, günlüğe kayıt, izin kontrolü veya özel operasyonlar yapabiliriz. Bu, veri tutarlılığını ve iş kurallarına uyumu sağlamanın yanı sıra geliştiricilere orijinal iş mantığını değiştirmeden ek işlevsellik eklemelerine olanak tanır.
2. Hook Kaydetme Yöntemi
2.1 Global Hooks ve Yerel Hooks
Global hooks (Runtime hooks
), grafikteki tüm işlemler için etkilidir. Uygulama genelindeki mantık eklemek için uygun olup, günlükleme ve izleme gibi işlevleri eklemek için uygundur. Yerel hooks (Schema hooks
), belirli tür şemaları içinde tanımlanır ve yalnızca şemanın türüyle eşleşen mutasyon işlemlerine uygulanır. Yerel hooks kullanarak, belirli düğüm türleriyle ilgili tüm mantık bir araya getirilerek, yani şema tanımında tek bir yerde merkezileştirilmiş olur.
2.2 Hooks Kaydetme Adımları
Kod içinde bir hook kaydetmek genellikle şu adımları içerir:
- Hook fonksiyonunu tanımlamak. Bu işlev bir
ent.Mutator
alır ve birent.Mutator
döndürür. Örneğin, basit bir günlükleme hook'u oluşturma:
logHook := func(next ent.Mutator) ent.Mutator {
return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
// Mutasyon işleminden önce logları yazdır
log.Printf("Mutasyon öncesi: Tür=%s, İşlem=%s\n", m.Type(), m.Op())
// Mutasyon işlemi gerçekleştir
v, err := next.Mutate(ctx, m)
// Mutasyon işleminden sonra logları yazdır
log.Printf("Mutasyon sonrası: Tür=%s, İşlem=%s\n", m.Type(), m.Op())
return v, err
})
}
- Hook'u istemciye kaydetmek. Global hook'lar için, istemciyi
Use
yöntemiyle kaydedebilirsiniz. Yerel hook'lar için, türdekiHooks
yöntemini kullanarak şemada kaydedebilirsiniz.
// Global hook kaydetme
client.Use(logHook)
// Yerel hook kaydetme, sadece 'User' türüne uygulanır
client.User.Use(func(next ent.Mutator) ent.Mutator {
return hook.UserFunc(func(ctx context.Context, m *ent.UserMutation) (ent.Value, error) {
// Özel mantık ekle
// ...
return next.Mutate(ctx, m)
})
})
- Birden çok hook'u zincirleyebilirsiniz ve kayıt sırasında bunlar sırayla çalıştırılır.
3. Hooks'un Çalışma Sırası
Hook'ların çalışma sırası, bunların istemci ile kaydedildiği sıraya göre belirlenir. Örneğin, client.Use(f, g, h)
örneğinde, mutasyon işleminde f(g(h(...)))
sırasında çalıştırılır. Bu örnekte, önce f
, ardından g
ve en sonunda da h
çalıştırılır.
Önemli bir nokta olarak runtime hook'lar (Runtime hooks
), schema hook'ların (Schema hooks
) önüne geçer. Bu, eğer g
ve h
hook'ları şema içinde tanımlanmışsa, f
ise client.Use(...)
kullanılarak kaydedilmişse, çalıştırma sırasının f(g(h(...)))
olacağı anlamına gelir. Bu, günlükleme gibi genel mantığın, diğer tüm hook'lardan önce çalıştırılmasını sağlar.
4. Hook'ların Neden Olduğu Sorunlarla Başa Çıkma
Hooks'u özelleştirirken, genellikle import döngüsü sorunuyla karşılaşabiliriz. Bu genellikle, ent/schema
paketinin ent
çekirdek paketini tanıtmasından kaynaklanır. Eğer ent
çekirdek paketi de ent/schema
'yı almaya çalışıyorsa, döngüsel bir bağımlılık oluşur.
Döngüsel Bağımlılıkların Sebepleri
Döngüsel bağımlılıklar genellikle, şema tanımları ile oluşturulan varlık kodu arasındaki karşılıklı bağımlılıklardan kaynaklanır. Bu, ent/schema
'nın ent
'e bağımlı olmasından kaynaklanır (çünkü ent
çerçevesi tarafından sağlanan türleri kullanması gerekir), ent
tarafından oluşturulan kodun da ent/schema
'ya bağımlı olmasından kaynaklanır (içinde tanımlanan şema bilgilerine erişmesi gerekir).
Dairesel Bağımlılıkları Çözme
Eğer dairesel bağımlılık hatası ile karşılaşırsan, aşağıdaki adımları izleyebilirsin:
- İlk olarak,
ent/schema
içinde kullanılan tüm kancaları yorum satırına al. - Daha sonra,
ent/schema
içinde tanımlanan özel tipleri yeni bir pakete taşı, örneğinent/schema/schematype
adında bir paket oluşturabilirsin. -
go generate ./...
komutunu çalıştırarakent
paketini güncelle, böylece şema içinde tiplerin referanslarını güncelle. Örneğin,schema.T
'yischematype.T
'ye değiştir. - Daha önce yorum satırına alınmış olan kancaların referanslarını aç ve tekrar
go generate ./...
komutunu çalıştır. Bu noktada, kod üretimi hatalar olmadan devam etmelidir.
Bu adımları izleyerek, Kancaların içeriği tarafından kaynaklanan dairesel bağımlılık sorununu çözebilir, şema mantığı ile Kancaların uygulanmasının sorunsuz bir şekilde devam etmesini sağlayabiliriz.
5. Hook Yardımcı Fonksiyonlarının Kullanımı
ent
çerçevesi, Kancaların çalışma zamanını kontrol etmemize yardımcı olan bir dizi kancayı destekleyen yardımcı fonksiyonlar sağlar. İşte sıkça kullanılan Hook yardımcı fonksiyonlarından bazı örnekler:
// Sadece UpdateOne ve DeleteOne işlemleri için HookA'yı icra et
hook.On(HookA(), ent.OpUpdateOne|ent.OpDeleteOne)
// Create işlemi sırasında HookB'nin çalıştırılmasını engelle
hook.Unless(HookB(), ent.OpCreate)
// HookC sadece "status" alanını değiştiren Mutation ve "dirty" alanını temizleyen durumlarda icra et
hook.If(HookC(), hook.And(hook.HasFields("status"), hook.HasClearedFields("dirty")))
// Update (çoklu) işlemlerinde "password" alanını değiştirmeyi yasakla
hook.If(
hook.FixedError(errors.New("password güncellemede düzenlenemez")),
hook.And(
hook.HasOp(ent.OpUpdate),
hook.Or(
hook.HasFields("password"),
hook.HasClearedFields("password"),
),
),
)
Bu yardımcı fonksiyonlar, farklı işlemler için Hook'ların etkinleşme koşullarını tam olarak kontrol etmemize olanak tanır.
6. İşlem Kancaları
İşlem Kancaları, bir işlem yapıldığında (Tx.Commit
) veya geri alındığında (Tx.Rollback
) belirli Kancaların çalıştırılmasına izin verir. Bu, veri tutarlılığını ve işlemlerin atomikliğini sağlamak için çok kullanışlıdır.
İşlem Kancaları Örneği
client.Tx(ctx, func(tx *ent.Tx) error {
// Bir işlem kancası kaydetme - hookBeforeCommit, commit'ten önce icra edilecektir.
tx.OnCommit(func(next ent.Committer) ent.Committer {
return ent.CommitFunc(func(ctx context.Context, tx *ent.Tx) error {
// Gerçek commit'ten önceki mantığı buraya yerleştirebilirsin.
fmt.Println("Commit'ten önce")
return next.Commit(ctx, tx)
})
})
// İşlem içerisinde bir dizi işlem gerçekleştir...
return nil
})
Yukarıdaki kod, bir işlemde commit'ten önce çalışacak bir işlem kancasını kaydetmenin nasıl yapıldığını göstermektedir. Bu kancalar, tüm veritabanı işlemleri gerçekleştirildikten sonra ve işlem gerçekten commit edilmeden önce çağrılacaktır.