Skip to content

Commit

Permalink
Feature/register di services (#4)
Browse files Browse the repository at this point in the history
* Registers resolver instance
* Adds ResolveRequiredService convenience function to resolve and safe-cast objects
* Adds test to cover new functionality
  • Loading branch information
matzefriedrich authored Jul 11, 2024
1 parent 0a873f0 commit b780037
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* Service registrations can be bundled in a `ModuleFunc` to register related types as a unit
* Service registry accepts object instances as singleton service registrations
* Adds the `ResolveRequiredService[T]` convenience function that resolves and safe-casts objects
* Registers resolver instance with the registry so that the `Resolver` object can be injected into factory and constructor methods

## v0.2.0 - 2024-07-11

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@ This dependency injection package may become your favorite ingredient for your G
- ✔️ Constructor injection
- ⏳ Injection via field initialization (requires annotation)
- ❌ Injection via setter methods
- ✔️ Convenience function to resolve and safe-cast objects: `ResolveRequiredService[T]`
- ✔️ Register types with a certain lifetime
- ✔️ Singleton
- ✔️ Register objects as singletons; use `RegisterInstance[T]` whereby `T` must be an interface type
- ✔️ Scoped (requires a certain context `NewScopedContext(context.Background))`; use `RegisterScoped`)
- ✔️ Transient
- ✔️ Bundle type registrations as modules to register them via `RegisterModule` as a unit
- ✔️ Resolve objects on-demand
- Allow consumption of `Resolver` in favor of custom factories
- ✔️ Allow consumption of `Resolver` in favor of custom factories
- ⏳ Validate registered services; fail early during application startup if missing registrations are encountered
- ⏳ Provide parameters for non-registered types and data
- ⏳ Support multiple service registrations for the same interface
- ⏳ Register named services (mutiple services), resolve via `func(key string) any`
- ⏳ Resolve list of service
Expand Down
4 changes: 3 additions & 1 deletion pkg/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ func NewServiceRegistry() types.ServiceRegistry {
}

func (s *serviceRegistry) BuildResolver() types.Resolver {
return NewResolver(s)
r := NewResolver(s)
_ = RegisterInstance(s, r)
return r
}

var _ types.ServiceRegistry = &serviceRegistry{}
Expand Down
1 change: 0 additions & 1 deletion pkg/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ func Test_Registry_RegisterInstance_registers_object(t *testing.T) {
assert.True(t, ok)
assert.NotNil(t, actual)
assert.Equal(t, reflect.ValueOf(instance).Pointer(), reflect.ValueOf(actual).Pointer())

}

type Foo interface {
Expand Down
13 changes: 13 additions & 0 deletions pkg/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@ type resolver struct {
globalInstances *internal.InstanceBag
}

func ResolveRequiredService[T any](resolver types.Resolver, ctx context.Context) (T, error) {
var nilInstance T
t := reflect.TypeOf((*T)(nil)).Elem()
if t.Kind() != reflect.Interface {
return nilInstance, types.NewResolverError(types.ErrorActivatorFunctionsMustReturnAnInterface)
}
resolve, err := resolver.Resolve(ctx, types.ServiceType[T]())
if err != nil {
return nilInstance, err
}
return resolve.(T), err
}

func NewResolver(registry types.ServiceRegistryAccessor) types.Resolver {
return &resolver{
registry: registry,
Expand Down
42 changes: 42 additions & 0 deletions pkg/resolver_factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package pkg

import (
"context"
"github.com/matzefriedrich/parsley/internal"
"github.com/matzefriedrich/parsley/pkg/types"
"github.com/stretchr/testify/assert"
"reflect"
"testing"
)

func Test_Resolver_ResolveRequiredService_factory_function_receives_current_resolver(t *testing.T) {

// Arrange
sut := NewServiceRegistry()
_ = RegisterSingleton(sut, NewFactory)

r := sut.BuildResolver()
ctx := internal.NewScopedContext(context.Background())

// Act
serviceFactory, _ := ResolveRequiredService[FactoryService](r, ctx)
f := serviceFactory.(*factory)
actual := f.resolver

// Assert
assert.NotNil(t, serviceFactory)

assert.NotNil(t, actual)
assert.Equal(t, reflect.ValueOf(r).Pointer(), reflect.ValueOf(actual).Pointer())
}

type factory struct {
resolver types.Resolver
}

type FactoryService interface {
}

func NewFactory(resolver types.Resolver) FactoryService {
return &factory{resolver: resolver}
}
2 changes: 2 additions & 0 deletions pkg/types/resolver_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const (
ErrorCircularDependencyDetected = "circular dependency detected"
ErrorCannotBuildDependencyGraph = "failed to build dependency graph"
ErrorInstanceCannotBeNil = "instance cannot be nil"
ErrorServiceTypeMustBeInterface = "service type must be an interface"
)

var (
Expand All @@ -18,6 +19,7 @@ var (
ErrCannotBuildDependencyGraph = errors.New(ErrorCannotBuildDependencyGraph)
ErrCircularDependencyDetected = errors.New(ErrorCircularDependencyDetected)
ErrInstanceCannotBeNil = errors.New(ErrorInstanceCannotBeNil)
ErrServiceTypeMustBeInterface = errors.New(ErrorServiceTypeMustBeInterface)
)

type ResolverError struct {
Expand Down

0 comments on commit b780037

Please sign in to comment.