#
Register Named Services
In Parsely, you can register services with specific names to support more granular control and flexible resolution of service instances. Named services are beneficial when you need to manage multiple implementations of the same interface or provide different configurations for different contexts.
When registering named services, you associate a unique name with each implementation. This allows you to resolve and retrieve the correct service based on its name, enhancing your application's flexibility and configurability.
Suppose you have an interface DataService
with two implementations: remoteDataService
and localDataService
.
package internal
import "fmt"
type DataService interface {
FetchData() string
}
type remoteDataService struct {
tenantId string
}
func (s *remoteDataService) FetchData() string {
return fmt.Sprintf("Data from remote server for tenant %s", s.tenantId)
}
type localDataService struct{}
func (s *localDataService) FetchData() string {
return "Data from local database"
}
func NewRemoteDataService() DataService {
return &remoteDataService{}
}
func NewRemoteDataServiceFactory() func(string) DataService {
return func(tenantId string) DataService {
return &remoteDataService{
tenantId: tenantId,
}
}
}
func NewLocalDataService() DataService {
return &localDataService{}
}
func NewLocalDataServiceFactory() func(string) DataService {
return func(_ string) DataService {
return &localDataService{}
}
}
#
Register named services
Register each service with a unique name using RegisterNamed
. This allows you to specify different names for each implementation and control their lifecycle.
registry := registration.NewServiceRegistry()
_ = features.RegisterNamed[internal.DataService](registry,
registration.NamedServiceRegistration("remote", internal.NewRemoteDataService, types.LifetimeTransient),
registration.NamedServiceRegistration("local", internal.NewLocalDataService, types.LifetimeTransient))
In this example, RegisterNamed
is used to register the constructor functions NewRemoteDataService
and NewLocalDataService
with the names remote
and local
, respectively. The LifetimeTransient
parameter indicates that each service instance is transient and will be created anew each time it is resolved.
Note Implementation types with names are automatically registered as an unnamed service with the desired interface type and lifetime scope. So, named services can be resolved (separately) by name or as a list of service instances per interface type.
#
Resolve and use named services
To resolve a named service, you use a factory function that takes the name of the service (a string
parameter) and returns an instance of the service along with any errors. This is done through the ResolveRequiredService
function:
serviceFactory, _ := resolving.ResolveRequiredService[func(string) (internal.DataService, error)](resolver, scope)
localDataService, err := serviceFactory("local")
if err != nil {
panic(err)
}
data := localDataService.FetchData()
fmt.Println(data)
Note You can combine the concept of factory functions and named services, but you must ensure that the signature of the factory function is the same for all named services.