في مجال الأنظمة الموزعة، غالباً ما تؤدي الاتصالات المباشرة من نقطة إلى نقطة بين الخدمات المصغرة إلى بنية "معقدة" (Spaghetti). ومع زيادة عدد الخدمات، تزداد تعقيدات إدارة التبعيات بشكل غير خطي. لمعالجة هذا، يلجأ المهندسون غالباً إلى نمط الوسيط (Mediator Pattern)، وهو نمط تصميم سلوكي يقلل من التداخل عن طريق منع الكائنات من الإشارة إلى بعضها البعض صراحةً. في هذا المنشور، سنستكشف كيفية تنفيذ هذا النمط بفعالية في Go لإنشاء طبقة اتصال للخدمات المصغرة تكون نظيفة وقابلة للصيانة وقابلة للتوسع.
مشكلة التداخل الشديد (Tight Coupling)
لنفترض سيناريو حيث تحتاج الخدمة A إلى التواصل مع الخدمات B و C و D. بدون وسيط، يجب أن تعرف الخدمة A عناوين الشبكة والبروتوكولات واستراتيجيات معالجة الأخطاء الخاصة بجميع الخدمات التابعة لها. هذا يخلق درجة عالية من التداخل. إذا تغيرت واجهة برمجة التطبيقات (API) للخدمة B، فيجب تحديث الخدمة A واختبارها وإعادة نشرها. هذا التداخل الشديد يجعل النظام هشاً وصعب التوسع. يحل نمط الوسيط هذه المشكلة عن طريق إدخال كائن وسيط يتعامل مع جميع التفاعلات. الآن، تحتاج الخدمة A فقط إلى معرفة كيفية التواصل مع الوسيط، وليس مع الخدمات الفردية.
تنفيذ الوسيط في Go
تشجع Go على البساطة والتكوين (Composition) بدلاً من تسلسلات الوراثة المعقدة. يمكننا تنفيذ نمط الوسيط باستخدام الواجهات (Interfaces) والهياكل (Structs). المكونات الأساسية هي واجهة Mediator، وواجهة Colleague (التي تمثل الخدمات)، والتنفيذ المحدد لـ Mediator.
// ColleagueInterface تحدد العقد لجميع الخدمات
// المشاركة في الوساطة.
type ColleagueInterface interface {
Notify(event string, data interface{})
}
// MediatorInterface تحدد العقد للوسيط.
type MediatorInterface interface {
Send(message string, colleague ColleagueInterface)
Register(colleague ColleagueInterface)
}
// ConcreteMediator ينسق الاتصال بين الزملاء.
type ConcreteMediator struct {
colleagues map[string]ColleagueInterface
}
func NewConcreteMediator() *ConcreteMediator {
return &ConcreteMediator{
colleagues: make(map[string]ColleagueInterface),
}
}
func (m *ConcreteMediator) Register(c ColleagueInterface) {
// في السيناريو الحقيقي، قد تستخدم الانعكاس أو سجل
// للكشف التلقائي عن الخدمات وتسجيلها.
if col, ok := c.(interface{ GetName() string }); ok {
m.colleagues[col.GetName()] = c
}
}
func (m *ConcreteMediator) Send(message string, colleague ColleagueInterface) {
targetName := colleague.GetName()
if target, exists := m.colleagues[targetName]; exists {
// محاكاة إرسال رسالة (على سبيل المثال، عبر gRPC أو Kafka)
fmt.Printf("Mediator sending '%s' to %s\n", message, targetName)
target.Notify("received", message)
} else {
fmt.Println("Target colleague not found.")
}
}
مثال عملي: سير معالجة الطلبات
لنطبق هذا على مثال عملي: نظام معالجة الطلبات. لدينا OrderService الذي يحتاج إلى إشعار InventoryService و PaymentService. بدلاً من أن يحتفظ OrderService بالإشارات إلى كليهما، فإنه يستخدم الوسيط.
// OrderService يعمل كزميل
type OrderService struct {
name string
mediator MediatorInterface
}
func NewOrderService(m MediatorInterface) *OrderService {
return &OrderService{
name: "OrderService",
mediator: m,
}
}
func (o *OrderService) GetName() string { return o.name }
func (o *OrderService) PlaceOrder() {
fmt.Println("Order placed. Notifying mediators...")
o.mediator.Send("order_created", o)
}
// PaymentService يعمل كزميل آخر
type PaymentService struct {
name string
}
func (p *PaymentService) GetName() string { return p.name }
func (p *PaymentService) Notify(event string, data interface{}) {
fmt.Printf("PaymentService received event: %s\n", event)
}
في الدالة main، نقوم بتسجيل هذه الخدمات مع الوسيط. عندما يضع OrderService طلباً، فإنه يطلق إشعاراً يقوم الوسيط بتوزيعه على المستمعين ذوي الصلة. يضمن هذا النهج أن يظل OrderService في غنى عن معرفة عدد بوابات الدفع أو أنظمة المخزون الموجودة، بل فقط أنه يمكنه طلب وساطة الإجراءات.
المزايا والمقايضات
يوفر اعتماد نمط الوسيط في Go مزايا كبيرة. فهو يبسط الكود عن طريق عزل منطق الاتصال المعقد، مما يسهل تعديله أو توسيعه. يمكنك إضافة خدمات جديدة دون لمس الخدمات الموجودة، مما يلتزم بمبدأ المفتوح/المغلق (Open/Closed Principle). ومع ذلك، هناك مقايضات. يمكن أن يصبح الوسيط نفسه "كائناً إلهياً" (God Object) إذا لم يتم تصميمه بعناية، حيث يتراكم عليه الكثير من المسؤوليات. من الضروري الحفاظ على الوسيط خفيف الوزن والنظر في تقسيمه إلى عدة وسطاء إذا كبر النظام كثيراً. بالإضافة إلى ذلك، قد يصبح تصحيح الأخطاء أكثر تحدياً قليلاً لأن تدفق التنفيذ غير مباشر. يعد استخدام التسجيل المنظم وعلامات التتبع ضرورياً للتخفيف من هذا الأمر.
الخاتمة
يُعد نمط الوسيط أداة قوية لإدارة التعقيد في خدمات Go المصغرة. من خلال تركيز منطق الاتصال، يعزز هذا النمط التداخل الضعيف ويعزز قابلية الصيانة. بينما يقدم نقطة تنسيق مركزية، يضمن التصميم الدقيق أن تظل هذه النقطة مرنة وقابلة للتوسع. بالنسبة للمطورين من المستوى المتوسط والمتقدم الذين يبحثون عن إعادة هيكلة التبعيات المتشابكة للخدمات، فإن تنفيذ نمط الوسيط هو خطوة استراتيجية نحو بنية أكثر متانة.