优化结构
This commit is contained in:
@@ -0,0 +1,283 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"log/slog"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"gorm.io/gorm"
|
||||
platformmq "kra/pkg/mq"
|
||||
platformws "kra/pkg/websocket"
|
||||
)
|
||||
|
||||
// FileStorage is the transport-neutral subset shared by every file storage
|
||||
// implementation. Domain operations that return business models remain in
|
||||
// the biz-owned FileStorage interface instead of making global depend on biz.
|
||||
type FileStorage interface {
|
||||
Open(context.Context, string) (io.ReadCloser, error)
|
||||
Delete(context.Context, string) error
|
||||
DeletePrefix(context.Context, string) error
|
||||
}
|
||||
|
||||
// Scheduler owns the lifecycle of the process-wide task scheduler. Scheduling
|
||||
// business operations remain behind the task domain interfaces.
|
||||
type Scheduler interface {
|
||||
Start(context.Context) error
|
||||
Stop(context.Context) error
|
||||
}
|
||||
|
||||
// Resources is an immutable snapshot from the registry's point of view.
|
||||
// Replacing a resource publishes a new snapshot; it does not close the old
|
||||
// resource because lifecycle ownership belongs to the composition root.
|
||||
type Resources struct {
|
||||
Logger *slog.Logger
|
||||
DB *gorm.DB
|
||||
NamedDBs map[string]*gorm.DB
|
||||
Redis redis.UniversalClient
|
||||
NamedRedis map[string]redis.UniversalClient
|
||||
Mongo *mongo.Client
|
||||
Storage FileStorage
|
||||
MQ platformmq.Registry
|
||||
WebSocket platformws.Hub
|
||||
Scheduler Scheduler
|
||||
}
|
||||
|
||||
// ResourceRegistry provides concurrency-safe access to process-wide runtime
|
||||
// resources. Prefer constructor injection for domain code; the registry is for
|
||||
// framework callbacks and integration points that cannot be wired directly.
|
||||
type ResourceRegistry struct {
|
||||
mu sync.RWMutex
|
||||
resources Resources
|
||||
}
|
||||
|
||||
func NewResourceRegistry() *ResourceRegistry {
|
||||
return &ResourceRegistry{resources: normalizeResources(Resources{})}
|
||||
}
|
||||
|
||||
func (r *ResourceRegistry) Snapshot() Resources {
|
||||
if r == nil {
|
||||
return normalizeResources(Resources{})
|
||||
}
|
||||
r.mu.RLock()
|
||||
resources := cloneResources(r.resources)
|
||||
r.mu.RUnlock()
|
||||
return resources
|
||||
}
|
||||
|
||||
func (r *ResourceRegistry) Replace(resources Resources) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
r.mu.Lock()
|
||||
r.resources = normalizeResources(resources)
|
||||
r.mu.Unlock()
|
||||
}
|
||||
|
||||
func (r *ResourceRegistry) Logger() *slog.Logger { return r.Snapshot().Logger }
|
||||
|
||||
func (r *ResourceRegistry) SetLogger(logger *slog.Logger) {
|
||||
r.update(func(resources *Resources) { resources.Logger = logger })
|
||||
}
|
||||
|
||||
func (r *ResourceRegistry) DB() *gorm.DB { return r.Snapshot().DB }
|
||||
|
||||
func (r *ResourceRegistry) SetDB(db *gorm.DB) {
|
||||
r.update(func(resources *Resources) { resources.DB = db })
|
||||
}
|
||||
|
||||
// NamedDB returns a configured secondary database by alias. An empty alias
|
||||
// resolves to the primary database.
|
||||
func (r *ResourceRegistry) NamedDB(name string) *gorm.DB {
|
||||
if name == "" {
|
||||
return r.DB()
|
||||
}
|
||||
return r.Snapshot().NamedDBs[name]
|
||||
}
|
||||
|
||||
// SetNamedDBs replaces the complete secondary-database alias set.
|
||||
func (r *ResourceRegistry) SetNamedDBs(databases map[string]*gorm.DB) {
|
||||
r.update(func(resources *Resources) { resources.NamedDBs = cloneDBMap(databases) })
|
||||
}
|
||||
|
||||
func (r *ResourceRegistry) Redis() redis.UniversalClient { return r.Snapshot().Redis }
|
||||
|
||||
func (r *ResourceRegistry) SetRedis(client redis.UniversalClient) {
|
||||
r.update(func(resources *Resources) { resources.Redis = client })
|
||||
}
|
||||
|
||||
// NamedRedis returns a configured secondary Redis client by alias. An empty
|
||||
// alias resolves to the primary Redis client.
|
||||
func (r *ResourceRegistry) NamedRedis(name string) redis.UniversalClient {
|
||||
if name == "" {
|
||||
return r.Redis()
|
||||
}
|
||||
return r.Snapshot().NamedRedis[name]
|
||||
}
|
||||
|
||||
// SetNamedRedis replaces the complete secondary-Redis alias set.
|
||||
func (r *ResourceRegistry) SetNamedRedis(clients map[string]redis.UniversalClient) {
|
||||
r.update(func(resources *Resources) { resources.NamedRedis = cloneRedisMap(clients) })
|
||||
}
|
||||
|
||||
func (r *ResourceRegistry) Mongo() *mongo.Client { return r.Snapshot().Mongo }
|
||||
|
||||
func (r *ResourceRegistry) SetMongo(client *mongo.Client) {
|
||||
r.update(func(resources *Resources) { resources.Mongo = client })
|
||||
}
|
||||
|
||||
func (r *ResourceRegistry) Storage() FileStorage { return r.Snapshot().Storage }
|
||||
|
||||
func (r *ResourceRegistry) SetStorage(storage FileStorage) {
|
||||
r.update(func(resources *Resources) { resources.Storage = storage })
|
||||
}
|
||||
|
||||
func (r *ResourceRegistry) MQ() platformmq.Registry { return r.Snapshot().MQ }
|
||||
|
||||
func (r *ResourceRegistry) SetMQ(registry platformmq.Registry) {
|
||||
r.update(func(resources *Resources) { resources.MQ = registry })
|
||||
}
|
||||
|
||||
func (r *ResourceRegistry) WebSocket() platformws.Hub { return r.Snapshot().WebSocket }
|
||||
|
||||
func (r *ResourceRegistry) SetWebSocket(hub platformws.Hub) {
|
||||
r.update(func(resources *Resources) { resources.WebSocket = hub })
|
||||
}
|
||||
|
||||
func (r *ResourceRegistry) Scheduler() Scheduler { return r.Snapshot().Scheduler }
|
||||
|
||||
func (r *ResourceRegistry) SetScheduler(scheduler Scheduler) {
|
||||
r.update(func(resources *Resources) { resources.Scheduler = scheduler })
|
||||
}
|
||||
|
||||
func (r *ResourceRegistry) update(change func(*Resources)) {
|
||||
if r == nil || change == nil {
|
||||
return
|
||||
}
|
||||
r.mu.Lock()
|
||||
resources := cloneResources(r.resources)
|
||||
change(&resources)
|
||||
r.resources = normalizeResources(resources)
|
||||
r.mu.Unlock()
|
||||
}
|
||||
|
||||
func normalizeResources(resources Resources) Resources {
|
||||
if resources.Logger == nil {
|
||||
resources.Logger = slog.Default()
|
||||
}
|
||||
resources.Redis = normalizeInterface(resources.Redis)
|
||||
resources.Storage = normalizeInterface(resources.Storage)
|
||||
resources.MQ = normalizeInterface(resources.MQ)
|
||||
resources.WebSocket = normalizeInterface(resources.WebSocket)
|
||||
resources.Scheduler = normalizeInterface(resources.Scheduler)
|
||||
resources.NamedDBs = cloneDBMap(resources.NamedDBs)
|
||||
resources.NamedRedis = cloneRedisMap(resources.NamedRedis)
|
||||
return resources
|
||||
}
|
||||
|
||||
func cloneResources(resources Resources) Resources {
|
||||
resources.NamedDBs = cloneDBMap(resources.NamedDBs)
|
||||
resources.NamedRedis = cloneRedisMap(resources.NamedRedis)
|
||||
return resources
|
||||
}
|
||||
|
||||
func cloneDBMap(source map[string]*gorm.DB) map[string]*gorm.DB {
|
||||
if len(source) == 0 {
|
||||
return nil
|
||||
}
|
||||
copyOf := make(map[string]*gorm.DB, len(source))
|
||||
for name, db := range source {
|
||||
if name != "" && db != nil {
|
||||
copyOf[name] = db
|
||||
}
|
||||
}
|
||||
if len(copyOf) == 0 {
|
||||
return nil
|
||||
}
|
||||
return copyOf
|
||||
}
|
||||
|
||||
func cloneRedisMap(source map[string]redis.UniversalClient) map[string]redis.UniversalClient {
|
||||
if len(source) == 0 {
|
||||
return nil
|
||||
}
|
||||
copyOf := make(map[string]redis.UniversalClient, len(source))
|
||||
for name, client := range source {
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
client = normalizeInterface(client)
|
||||
if client != nil {
|
||||
copyOf[name] = client
|
||||
}
|
||||
}
|
||||
if len(copyOf) == 0 {
|
||||
return nil
|
||||
}
|
||||
return copyOf
|
||||
}
|
||||
|
||||
func normalizeInterface[T any](value T) T {
|
||||
reflected := reflect.ValueOf(value)
|
||||
if reflected.IsValid() {
|
||||
switch reflected.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
|
||||
if !reflected.IsNil() {
|
||||
return value
|
||||
}
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
var zero T
|
||||
return zero
|
||||
}
|
||||
|
||||
var defaultResources = NewResourceRegistry()
|
||||
|
||||
func ResourceSnapshot() Resources { return defaultResources.Snapshot() }
|
||||
|
||||
func ReplaceResources(resources Resources) { defaultResources.Replace(resources) }
|
||||
|
||||
func Logger() *slog.Logger { return defaultResources.Logger() }
|
||||
|
||||
func SetLogger(logger *slog.Logger) { defaultResources.SetLogger(logger) }
|
||||
|
||||
func DB() *gorm.DB { return defaultResources.DB() }
|
||||
|
||||
func SetDB(db *gorm.DB) { defaultResources.SetDB(db) }
|
||||
|
||||
func NamedDB(name string) *gorm.DB { return defaultResources.NamedDB(name) }
|
||||
|
||||
func SetNamedDBs(databases map[string]*gorm.DB) { defaultResources.SetNamedDBs(databases) }
|
||||
|
||||
func Redis() redis.UniversalClient { return defaultResources.Redis() }
|
||||
|
||||
func SetRedis(client redis.UniversalClient) { defaultResources.SetRedis(client) }
|
||||
|
||||
func NamedRedis(name string) redis.UniversalClient { return defaultResources.NamedRedis(name) }
|
||||
|
||||
func SetNamedRedis(clients map[string]redis.UniversalClient) { defaultResources.SetNamedRedis(clients) }
|
||||
|
||||
func Mongo() *mongo.Client { return defaultResources.Mongo() }
|
||||
|
||||
func SetMongo(client *mongo.Client) { defaultResources.SetMongo(client) }
|
||||
|
||||
func Storage() FileStorage { return defaultResources.Storage() }
|
||||
|
||||
func SetStorage(storage FileStorage) { defaultResources.SetStorage(storage) }
|
||||
|
||||
func MQ() platformmq.Registry { return defaultResources.MQ() }
|
||||
|
||||
func SetMQ(registry platformmq.Registry) { defaultResources.SetMQ(registry) }
|
||||
|
||||
func WebSocket() platformws.Hub { return defaultResources.WebSocket() }
|
||||
|
||||
func SetWebSocket(hub platformws.Hub) { defaultResources.SetWebSocket(hub) }
|
||||
|
||||
func TaskScheduler() Scheduler { return defaultResources.Scheduler() }
|
||||
|
||||
func SetTaskScheduler(scheduler Scheduler) { defaultResources.SetScheduler(scheduler) }
|
||||
Reference in New Issue
Block a user