Go Programming

جدا کردن میکروسرویس‌ها با الگوی واسط در Go

در دنیای سیستم‌های توزیع‌شده، ارتباط مستقیم نقطه به نقطه بین میکروسرویس‌ها اغلب منجر به معماری «اسپاگتی» می‌شود. با افزایش تعداد سرویس‌ها، پیچیدگی مدیریت وابستگی‌ها به صورت غیرخطی افزایش می‌یابد. برای مقابله با این موضوع، معماران اغلب به الگوی واسط (Mediator Pattern) روی می‌آورند؛ یک الگوی طراحی رفتاری که با جلوگیری از ارجاع صریح اشیاء به یکدیگر، وابستگی‌ها را کاهش می‌دهد. در این پست، بررسی می‌کنیم که چگونه می‌توان این الگو را به طور مؤثر در Go پیاده‌سازی کرد تا لایه ارتباطی میکروسرویس‌ها تمیز، قابل نگهداری و مقیاس‌پذیر باشد.

مشکل وابستگی شدید (Tight Coupling)

سناریویی را در نظر بگیرید که در آن سرویس A نیاز دارد با سرویس‌های B، C و D ارتباط برقرار کند. بدون وجود یک واسط، سرویس A باید آدرس‌های شبکه، پروتکل‌ها و استراتژی‌های مدیریت خطای هر سه سرویس downstream را بداند. این موضوع باعث ایجاد درجه بالایی از وابستگی می‌شود. اگر API سرویس B تغییر کند، سرویس A باید به‌روزرسانی، تست و مجدداً استقرار یابد. این وابستگی شدید، سیستم را شکننده و مقیاس‌پذیری دشوار می‌کند. الگوی واسط این مشکل را با معرفی یک شیء واسطه‌ای که تمام تعاملات را مدیریت می‌کند، حل می‌نماید. اکنون، سرویس A تنها نیاز دارد بداند چگونه با واسط ارتباط برقرار کند، نه با سرویس‌های فردی.

پیاده‌سازی واسط در Go

Go سادگی و ترکیب‌پذیری (Composition) را بر سلسله‌مراتب ارث‌بری پیچیده ترجیح می‌دهد. ما می‌توانیم الگوی واسط را با استفاده از اینترفیس‌ها و ساختارها (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) {
	// در یک سناریوی واقعی، ممکن است از بازتاب (Reflection) یا یک رجیستری
	// برای تشخیص خودکار و ثبت سرویس‌ها استفاده کنید.
	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 کاملاً از تعداد دروازه‌های پرداخت یا سیستم‌های موجودیت (Inventory) آگاه نیست، بلکه تنها می‌داند که می‌تواند درخواست واسطه‌گری اقدامات را داشته باشد.

مزایا و ملاحظات

پذیرش الگوی واسط در Go مزایای قابل توجهی ارائه می‌دهد. این الگو با متمرکز کردن منطق ارتباطات پیچیده، کد را ساده‌تر کرده و اصلاح یا گسترش آن را آسان‌تر می‌سازد. شما می‌توانید سرویس‌های جدید را بدون دستکاری سرویس‌های موجود اضافه کنید که به اصل باز/بسته (Open/Closed Principle) پایبند است. با این حال، ملاحظات و هزینه‌هایی نیز وجود دارد. واسط می‌تواند اگر به دقت طراحی نشود، به یک «شیء خدا» (God Object) تبدیل شود که مسئولیت‌های بیش از حدی را انباشته می‌کند. بسیار مهم است که واسط سبک نگه داشته شود و در صورت بزرگ شدن بیش از حد سیستم، در نظر گرفته شود که آن را به چندین واسط تقسیم کنید. علاوه بر این، عیب‌یابی ممکن است کمی چالش‌برانگیزتر شود زیرا جریان اجرا غیرمستقیم است. استفاده از لاگ‌نویسی ساختاریافته و شناسه‌های ردیابی (Tracing IDs) برای کاهش این مشکل ضروری است.

نتیجه‌گیری

الگوی واسط ابزاری قدرتمند برای مدیریت پیچیدگی در میکروسرویس‌های Go است. با متمرکز کردن منطق ارتباطات، این الگو وابستگی ضعیف را ترویج داده و قابلیت نگهداری را بهبود می‌بخشد. اگرچه این الگو یک نقطه مرکزی برای هماهنگی معرفی می‌کند، اما طراحی دقیق تضمین می‌کند که این نقطه انعطاف‌پذیر و مقیاس‌پذیر باقی بماند. برای توسعه‌دهندگان متوسط و پیشرفته‌ای که به دنبال بازسازی وابستگی‌های درهم‌تنیده سرویس‌ها هستند، پیاده‌سازی الگوی واسط گامی استراتژیک به سمت معماری قوی‌تر است.

Share: