164 lines
4.3 KiB
Go
164 lines
4.3 KiB
Go
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 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()
|
|
|
|
// Install publishes resources assembled by the composition root and returns
|
|
// the process-wide registry for lifecycle wiring.
|
|
func Install(resources Resources) *ResourceRegistry {
|
|
defaultResources.Replace(resources)
|
|
return defaultResources
|
|
}
|
|
|
|
func DefaultRegistry() *ResourceRegistry { return defaultResources }
|
|
|
|
func ResourceSnapshot() Resources { return defaultResources.Snapshot() }
|