آليات الخطافات
آليات الخطافات هي طريقة لإضافة منطق مخصص قبل أو بعد حدوث تغييرات معينة في عمليات قاعدة البيانات. عند تعديل مخطط قاعدة البيانات، مثل إضافة فُقرات جديدة، حذف حواف بين الفُقرات، أو حذف عدة فُقرات، يمكننا استخدام آليات الخطافات لأداء التحقق من البيانات، تسجيل السجلات، فحص الصلاحيات، أو أي عمليات مخصصة. هذا أمر حيوي لضمان اتساق البيانات وامتثالها لقواعد العمل، مع السماح للمطورين أيضًا بإضافة وظائف إضافية دون تغيير المنطق التجاري الأصلي.
2. طريقة تسجيل الخطافات
2.1 الخطافات العامة والمحلية
تُظهر الخطافات العامة ("خطافات التشغيل") تأثيرها على جميع أنواع العمليات في الرسم البياني. فهي مناسبة لإضافة منطق إلى التطبيق بأكمله، مثل التسجيل والمراقبة. بينما تُعرَّف الخطافات المحلية ("خطافات المخطط") داخل تعاريف أنواع محددة وتنطبق فقط على عمليات الطفح التي تتطابق مع نوع المخطط. باستخدام الخطافات المحلية، يُمكن ترك كل منطق المتعلق بأنواع الفُقرات المحددة في مكان واحد، ألا وهو في تعريف المخطط.
2.2 خطوات تسجيل الخطافات
ينطوي تسجيل خطاف في الكود عادةً على الخطوات التالية:
-
تعريف وظيفة الخطاف. تأخذ هذه الوظيفة
ent.Mutator
وتعيدent.Mutator
. على سبيل المثال، إنشاء خطاف تسجيل بسيط:logHook := func(next ent.Mutator) ent.Mutator { return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) { // طباعة السجلات قبل عملية الطفح log.Printf("قبل التغيير: النوع=%s، العملية=%s\n", m.Type(), m.Op()) // تنفيذ عملية الطفح v, err := next.Mutate(ctx, m) // طباعة السجلات بعد عملية الطفح log.Printf("بعد التغيير: النوع=%s، العملية=%s\n", m.Type(), m.Op()) return v, err }) }
-
تسجيل الخطاف مع العميل. بالنسبة للخطافات العامة، يُمكنك تسجيلها باستخدام الطريقة
Use
للعميل. بالنسبة للخطافات المحلية، يُمكنك تسجيلها في المخطط باستخدام الطريقةHooks
للنوع.// تسجيل خطاف عام client.Use(logHook) // تسجيل خطاف محلي، يُطبق فقط على نوع المستخدم client.User.Use(func(next ent.Mutator) ent.Mutator { return hook.UserFunc(func(ctx context.Context, m *ent.UserMutation) (ent.Value, error) { // إضافة منطق محدد // ... return next.Mutate(ctx, m) }) })
-
يُمكن ربط عدة خطافات، وسيتم تنفيذها بترتيب التسجيل.
3. ترتيب تنفيذ الخطافات
يُحدد ترتيب تنفيذ الخطافات بالتسجيل مع العميل. على سبيل المثال، client.Use(f, g, h)
سيُنفَّذ عملية الطفح بالتسلسل f(g(h(...)))
. في هذا المثال، f
هو أول خطاف يتم تنفيذه، تليه g
، ثم h
أخيرًا.
من المهم أن نلاحظ أن الخطافات العامة ("خطافات التشغيل") تأخذ الأسبقية على الخطافات المحلية ("خطافات المخطط"). يعني هذا أنه إذا كانت g
و h
خطافات مُعرَفة في المخطط بينما تم تسجيل f
باستخدام client.Use(...)، فإن ترتيب التنفيذ سيكون
f(g(h(...)))`. يضمن هذا أن يتم تنفيذ المنطق العام، مثل التسجيل، قبل كل الخطافات الأخرى.
4. التعامل مع المشاكل الناجمة عن الخطافات
عند تخصيص عمليات قاعدة البيانات باستخدام الخطافات، قد نواجه مشكلة الدورات الاستيرادية. يحدث هذا عادةً عند محاولة استخدام خطافات المخطط، حيث يمكن لحزمة ent/schema
أن تقدم حزمة ent
الأساسية. إذا حاولت حزمة ent
الأساسية أيضًا استيراد ent/schema
، فإنه سيتم تكوين تبادل الاعتمادات.
أسباب الاعتماديات التبادلية
غالبًا ما تنشأ الاعتماديات التبادلية من الاعتماديات ذات الاتجاهين بين تعريفات المخطط والكود المولَّد للكيان. يعني هذا أن حزمة ent/schema
تعتمد على ent
(لأنها تحتاج إلى استخدام أنواع توفرها إطار العمل ent
، بينما يعتمد الكود المولّد بواسطة ent
أيضًا على ent/schema
(لأنه يحتاج إلى الوصول إلى معلومات المخطط المعرفة ضمنها).
حل مشكلة التبادل الدائري
إذا واجهت خطأ في التبادل الدائري، يمكنك اتباع هذه الخطوات:
- أولاً، قم بتعليق جميع الهوكات المستخدمة في
ent/schema
. - بعد ذلك، قم بنقل أنواع البيانات المخصصة المعرفة في
ent/schema
إلى حزمة جديدة، على سبيل المثال، يمكنك إنشاء حزمة باسمent/schema/schematype
. - قم بتشغيل الأمر
go generate ./...
لتحديث حزمةent
، بحيث تشير إلى مسار الحزمة الجديدة، وتحديث مراجع الأنواع في النموذج. على سبيل المثال، قم بتغييرschema.T
إلىschematype.T
. - قم بإلغاء تعليق مراجع هوكات التي تم تعليقها سابقًا وقم بتشغيل الأمر
go generate ./...
مرة أخرى. في هذه النقطة، يجب أن يتابع إنشاء الكود دون وجود أخطاء.
من خلال اتباع هذه الخطوات، يمكننا حل مشكلة التبادل الدائري الناجمة عن استيرادات الهوكات، مما يضمن سير منطق النموذج وتنفيذ الهوكات بسلاسة.
5. استخدام دوال مساعدة للهوكات
إطار عمل ent
يوفر مجموعة من دوال المساعدة للهوكات، والتي يمكن أن تساعدنا في التحكم في توقيت تنفيذ الهوكات. فيما يلي بعض أمثلة على دوال المساعدة للهوكات المستخدمة بشكل شائع:
// تنفيذ الهوك A فقط لعمليات UpdateOne و DeleteOne
hook.On(HookA(), ent.OpUpdateOne|ent.OpDeleteOne)
// عدم تنفيذ الهوك B أثناء عملية الإنشاء
hook.Unless(HookB(), ent.OpCreate)
// تنفيذ الهوك C فقط عندما يقوم التحويل بتعديل حقل "status" ومسح حقل "dirty"
hook.If(HookC(), hook.And(hook.HasFields("status"), hook.HasClearedFields("dirty")))
// منع تعديل حقل "password" في عمليات التحديث المتعددة
hook.If(
hook.FixedError(errors.New("يتعذر تحرير كلمة المرور عند التحديث المتعدد")),
hook.And(
hook.HasOp(ent.OpUpdate),
hook.Or(
hook.HasFields("password"),
hook.HasClearedFields("password"),
),
),
)
هذه الدوال المساعدة تتيح لنا التحكم بدقة في شروط تنشيط الهوكات لعمليات مختلفة.
6. هوكات العمليات المالية
تتيح هوكات العمليات المالية تنفيذ هوكات معينة عندما يتم تأكيد عملية مالية (Tx.Commit
) أو إلغاؤها (Tx.Rollback
). هذا مفيد جدًا لضمان تماسك البيانات وذر اتمام العمليات.
مثال على هوكات العمليات المالية
client.Tx(ctx, func(tx *ent.Tx) error {
// تسجيل هوك عملية مالية - سيتم تنفيذ الهوك قبل التأكيد.
tx.OnCommit(func(next ent.Committer) ent.Committer {
return ent.CommitFunc(func(ctx context.Context, tx *ent.Tx) error {
// يمكن وضع المنطق قبل التأكيد الفعلي هنا.
fmt.Println("قبل التأكيد");
return next.Commit(ctx, tx);
});
});
// قم بتنفيذ سلسلة من العمليات داخل العملية المالية...
return nil;
})
يُظهر الكود أعلاه كيفية تسجيل هوك عملية مالية للتشغيل قبل التأكيد في عملية مالية. سيتم استدعاء هذا الهوك بعد تنفيذ جميع عمليات قاعدة البيانات وقبل تأكيد العملية المالية فعليًا.