Dağıtık sistemlerin modern manzarasında, mikroservisler arasında gevşek bağları korumak sadece en iyi bir uygulama değil; ölçeklenebilirlik ve sürdürülebilirlik için bir zorunluluktur. Go (Golang), sadeliği ve performansı ile ünlü olsa da, tasarım konusundaki öz dille yaklaşımı geleneksel nesne yönelimli desenlerden genellikle farklılaşır. Bu yazıda, dayanıklı ve ayrık mimariler oluşturmak için Go'da Gözlemci ve Strateji desenlerini etkili bir şekilde nasıl uygulayacağımızı keşfedeceğiz.
Mikroservislerde Bağlılık Sorunu
Mikroservisler bağımsızlık üzerinde gelişir. Ancak, servisler genellikle durum değişikliklerine tepki vermeye (Gözlemci) veya bağlama göre davranışlarını uyarlamaya (Strateji) ihtiyaç duyar. Miras veya global olay sunucularına dayanan geleneksel uygulamalar, sıkı bağlılıklara yol açabilir; bu da testleri zorlaştırır ve arızaların yayılma çapını artırır. Go'nun arayüz tabanlı çok biçimliliği ve birinci sınıf fonksiyonları, bu desenler için zarif ve hafif alternatifler sunar.
Go'da Gözlemci Deseninin Uygulanması
Gözlemci deseni, bir nesne durumunu değiştirdiğinde, tüm bağlı nesnelerin otomatik olarak bildirildiği ve güncellendiği nesneler arasında bir-çok bağımlılık tanımlar. Go'da genellikle ağır miras hiyerarşilerinden kaçınırız. Bunun yerine, engellemeyen bir olay dinleyici sistemi oluşturmak için arayüzleri ve gorutinleri kullanırız.
Bir sipariş verildiğinde OrderService'un birden fazla alt servis (e-posta, envanter, analitik) bildirmesi gerektiği bir senaryoyu ele alalım. Basit bir Subscriber arayüzü ve abonelikleri yönetmek için bir EventHub tanımlayabiliriz.
package event
// Subscriber, olay dinleyicileri için arayüzü tanımlar
type Subscriber interface {
Handle(event string, data interface{})
}
// Hub, abonelikleri yönetir ve olayları yayınlar
type Hub struct {
subscribers map[string][]Subscriber
mu sync.Mutex
}
func NewHub() *Hub {
return &Hub{
subscribers: make(map[string][]Subscriber),
}
}
func (h *Hub) Subscribe(event string, sub Subscriber) {
h.mu.Lock()
defer h.mu.Unlock()
h.subscribers[event] = append(h.subscribers[event], sub)
}
func (h *Hub) Broadcast(event string, data interface{}) {
h.mu.Lock()
subs := make([]Subscriber, len(h.subscribers[event]))
copy(subs, h.subscribers[event])
h.mu.Unlock()
// Yayıncının engellenmemesi için asenkron olarak işleyin
for _, sub := range subs {
go sub.Handle(event, data)
}
}
Bu uygulama, aboneler haritasını korumak için bir mutex kullanır ve her bildirim için bir gorutin başlatır. Bu, yayıncının (OrderService) yavaş abonelerin (ağa bağlı bir e-posta servisi gibi) tamamlanmasını beklemesini engeller ve böylece sistem yanıt süresini artırır.
Esnek Mantık İçin Strateji Desenini Benimseme
Strateji deseni, bir algoritmanın davranışının çalışma zamanında seçilmesini sağlar. Go'da bu, Java veya C++'a kıyasla genellikle arayüzlerle tek bir yöntem kullanılarak veya doğrudan fonksiyon türlerinin parametre olarak geçirilmesiyle daha öz bir şekilde gerçekleştirilir.
Farklı kullanıcı seviyelerine (VIP, Normal, Yeni) göre indirim hesaplayan bir PricingService uygulayalım. if/else ifadeleri kullanmak yerine, bir DiscountStrategy arayüzü tanımlayalım.
package pricing
import "fmt"
// DiscountStrategy, indirim hesaplaması için sözleşmeyi tanımlar
type DiscountStrategy interface {
Calculate(basePrice float64) float64
}
// VipDiscount, %20 indirim uygular
type VipDiscount struct{}
func (v VipDiscount) Calculate(basePrice float64) float64 {
return basePrice * 0.8
}
// StandardDiscount, indirim uygulamaz
type StandardDiscount struct{}
func (s StandardDiscount) Calculate(basePrice float64) float64 {
return basePrice
}
// PriceEngine, nihai fiyatı belirlemek için bir strateji kullanır
type PriceEngine struct {
strategy DiscountStrategy
}
func NewPriceEngine(s DiscountStrategy) *PriceEngine {
return &PriceEngine{strategy: s}
}
func (p *PriceEngine) GetFinalPrice(basePrice float64) float64 {
return p.strategy.Calculate(basePrice)
}
Stratejiyi PriceEngine'a enjekte ederek, fiyatlandırma mantığını servis mantığından ayırırız. Bu, birim testlerini basitleştirir; motorun kodunu değiştirmede farklı indirim stratejilerini kolayca taklit edebilirsiniz.
Güçlü Mimari İçin Desenleri Birleştirme
Bu desenlerin gerçek gücü, birleştirildiklerinde ortaya çıkar. OrderService'unuzun, yayımlanacak olayın türünü belirlemek için Strateji desenini kullandığını ve ardından servisleri bildirmek için Gözlemci desenini kullandığını hayal edin. Gözlemci, bu olayları özel işleme için diğer Strateji-tabanlı işleyicilere iletebilir.
Örneğin, bir analitik mikroservisi order.placed olayına abone olabilir. Bildirildikten sonra, sistem yüküne bağlı olarak RealTimeStrategy veya BatchStrategy arasında geçiş yapabilir.
Sonuç
Go'da Gözlemci ve Strateji desenlerini uygulamak, klasik OOP'den arayüz odaklı ve işlevsel tasarıma bir zihniyet değişikliği gerektirir. Go'nun eşzamanlılık ilkelere ve tür sisteminden yararlanarak, yalnızca ayrık olmakla kalmayan, aynı zamanda yüksek performanslı ve test edilebilir mikroservisler oluşturabilirsiniz. Bu desenler karmaşıklığı azaltarak, bağımlı sistemleri bozma korkusu olmadan ekibinizin daha hızlı iterasyon yapmasına olanak tanır. Go mikroservis mimarinizin tam potansiyelini açığa çıkarmak için bu öz yaklaşımları benimseyin.