package sl import ( "fmt" "log" ) type provider struct { initialized bool service Service } type ServiceLocator struct { providers map[string]*provider hooks map[string][]Service } type Service interface { Initialize(l *ServiceLocator) error } func New() *ServiceLocator { return &ServiceLocator{ providers: map[string]*provider{}, hooks: map[string][]Service{}, } } func getTypeName[T any]() string { var v T return fmt.Sprintf(`%T`, &v)[1:] } // Inject will set the implementation for "S" to "value" (the service will be initialized when needed after all of its dependencies) func Inject[S Service](l *ServiceLocator, value S) { key := getTypeName[S]() log.Printf(`injecting value of type %T for interface %s`, value, key) l.providers[key] = &provider{false, value} } // InjectValue will set the implementation for "S" to "value" and mark this service as already initialized (as this is just a constant) func InjectValue[S Service](l *ServiceLocator, value S) S { key := getTypeName[S]() log.Printf(`injecting value of type %T for interface %s`, value, key) l.providers[key] = &provider{true, value} return value } // Use will retrive from the service locator the implementation set for the type "T" and initialize the service if yet to be initialized func Use[T Service](l *ServiceLocator) (T, error) { provider, ok := l.providers[getTypeName[T]()] if !ok { var zero T return zero, fmt.Errorf(`no injected value for type "%T"`, zero) } if provider.initialized { service := provider.service.(T) return service, nil } log.Printf(`initializing %T`, provider.service) if err := provider.service.Initialize(l); err != nil { var zero T return zero, err } provider.initialized = true service := provider.service.(T) return service, nil }