Комментарии (3)
Ответ сгенерирован нейросетью и может содержать ошибки
Реестры (Registry) в Go
В контексте Go-разработки под Registry обычно понимают паттерн проектирования "Реестр" (или "Регистр"), который представляет собой централизованное хранилище объектов, доступное глобально или в рамках определенного контекста. Это структурный паттерн, используемый для управления зависимостями, сервисами или компонентами системы.
Основные типы реестров в Go
1. Service Registry (Реестр сервисов)
Используется в микросервисной архитектуре для отслеживания доступных экземпляров сервисов.
package main
import "sync"
type ServiceRegistry struct {
mu sync.RWMutex
services map[string]string // serviceName -> endpoint
}
func NewServiceRegistry() *ServiceRegistry {
return &ServiceRegistry{
services: make(map[string]string),
}
}
func (sr *ServiceRegistry) Register(serviceName, endpoint string) {
sr.mu.Lock()
defer sr.mu.Unlock()
sr.services[serviceName] = endpoint
}
func (sr *ServiceRegistry) GetEndpoint(serviceName string) (string, bool) {
sr.mu.RLock()
defer sr.mu.RUnlock()
endpoint, exists := sr.services[serviceName]
return endpoint, exists
}
2. Dependency Injection Registry
Реестр для управления зависимостями в приложениях с внедрением зависимостей (DI).
package di
import (
"fmt"
"sync"
)
type Container struct {
mu sync.RWMutex
dependencies map[string]interface{}
factories map[string]func() interface{}
}
func NewContainer() *Container {
return &Container{
dependencies: make(map[string]interface{}),
factories: make(map[string]func() interface{}),
}
}
func (c *Container) RegisterSingleton(name string, instance interface{}) {
c.mu.Lock()
defer c.mu.Unlock()
c.dependencies[name] = instance
}
func (c *Container) RegisterFactory(name string, factory func() interface{}) {
c.mu.Lock()
defer c.mu.Unlock()
c.factories[name] = factory
}
func (c *Container) Get(name string) (interface{}, error) {
c.mu.RLock()
defer c.mu.RUnlock()
if dep, exists := c.dependencies[name]; exists {
return dep, nil
}
if factory, exists := c.factories[name]; exists {
return factory(), nil
}
return nil, fmt.Errorf("dependency %s not found", name)
}
3. Registry Pattern для конфигурации
Централизованное хранение конфигурации приложения.
package config
import (
"encoding/json"
"os"
"sync"
)
type ConfigRegistry struct {
mu sync.RWMutex
config map[string]interface{}
}
func (cr *ConfigRegistry) LoadFromFile(path string) error {
cr.mu.Lock()
defer cr.mu.Unlock()
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
return json.NewDecoder(file).Decode(&cr.config)
}
func (cr *ConfigRegistry) Get(key string) interface{} {
cr.mu.RLock()
defer cr.mu.RUnlock()
return cr.config[key]
}
func (cr *ConfigRegistry) Set(key string, value interface{}) {
cr.mu.Lock()
defer cr.mu.Unlock()
cr.config[key] = value
}
4. Plugin Registry
Реестр для динамической загрузки и управления плагинами.
package plugin
import (
"plugin"
"sync"
)
type PluginRegistry struct {
mu sync.RWMutex
plugins map[string]*plugin.Plugin
}
func (pr *PluginRegistry) LoadPlugin(name, path string) error {
pr.mu.Lock()
defer pr.mu.Unlock()
p, err := plugin.Open(path)
if err != nil {
return err
}
pr.plugins[name] = p
return nil
}
func (pr *PluginRegistry) GetSymbol(pluginName, symbolName string) (interface{}, error) {
pr.mu.RLock()
defer pr.mu.RUnlock()
p, exists := pr.plugins[pluginName]
if !exists {
return nil, ErrPluginNotFound
}
return p.Lookup(symbolName)
}
Ключевые особенности и лучшие практики
Потокобезопасность
- Все операции с реестром должны быть thread-safe
- Используйте
sync.RWMutexдля конкурентного доступа - Чтение должно блокироваться только на запись
Инициализация
// Ленивая инициализация
var (
registry *ServiceRegistry
once sync.Once
)
func GetRegistry() *ServiceRegistry {
once.Do(func() {
registry = NewServiceRegistry()
})
return registry
}
Жизненный цикл
- Регистрация компонентов при инициализации
- Резолвинг зависимостей во время выполнения
- Очистка при завершении работы приложения
Преимущества использования Registry
- Централизованное управление зависимостями
- Упрощение тестирования через моки и стабы
- Гибкость в замене реализаций
- Снижение связности между компонентами
Недостатки и ограничения
- Глобальное состояние может усложнить отладку
- Потенциальные утечки памяти при неправильном управлении жизненным циклом
- Сложность при использовании в конкурентных сценариях
Альтернативы и смежные подходы
- Функциональная зависимость вместо глобального реестра
- Контекст (context.Context) для передачи зависимостей
- Специализированные библиотеки: Uber Dig, Google Wire
- Встроенные механизмы Go:
init()функции, глобальные переменные (с осторожностью)
Пример комплексного использования
package app
import (
"database/sql"
"log"
"sync"
)
type AppRegistry struct {
db *sql.DB
cache Cache
logger *log.Logger
mu sync.RWMutex
}
func (ar *AppRegistry) DB() *sql.DB {
ar.mu.RLock()
defer ar.mu.RUnlock()
return ar.db
}
func (ar *AppRegistry) SetDB(db *sql.DB) {
ar.mu.Lock()
defer ar.mu.Unlock()
ar.db = db
}
// Аналогичные методы для cache и logger
Registry в Go — это мощный паттерн, который требует аккуратного использования. При правильной реализации он значительно упрощает архитектуру приложения, но при неправильном — может создать серьезные проблемы с поддерживаемостью и тестированием. Ключевой принцип — баланс между удобством использования и соблюдением принципов чистой архитектуры.