diff --git a/cli/di.go b/cli/di.go index 9565d97..6a14d27 100644 --- a/cli/di.go +++ b/cli/di.go @@ -62,7 +62,7 @@ func generateMakeProvider(f *os.File, i int) { func generateMakeTokenWithDefault(f *os.File, i int) { // non generic version - fmt.Fprintf(f, "\n// MakeTokenWithDefault%d creates an [InjectionToken] with a default implementation with %d dependenciess\n", i, i) + fmt.Fprintf(f, "\n// MakeTokenWithDefault%d creates an [InjectionToken] with a default implementation with %d dependencies\n", i, i) fmt.Fprintf(f, "func MakeTokenWithDefault%d[", i) for j := 0; j < i; j++ { if j > 0 { diff --git a/di/doc.go b/di/doc.go index 9ff0616..eafa9e3 100644 --- a/di/doc.go +++ b/di/doc.go @@ -14,6 +14,21 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Package implements functions and data types supporting dependency injection patterns +// +// The fundamental building block is the concept of a [Dependency]. This describes the abstract concept of a function, service or value together with its type. +// Examples for dependencies can be as simple as configuration values such as the API URL for a service, the current username, a [Dependency] could be the map +// of the configuration environment, an http client or as complex as a service interface. Important is that a [Dependency] only defines the concept but +// not the implementation. +// +// The implementation of a [Dependency] is called a [Provider], the dependency of an `API URL` could e.g. be realized by a provider that consults the environment to read the information +// or a config file or simply hardcode it. +// In many cases the implementation of a [DIE.Provider] depends in turn on other [Dependency]s (but never directly on other [DIE.Provider]s), a provider for an `API URL` that reads +// the information from the environment would e.g. depend on a [Dependency] that represents this environment. +// +// It is the resposibility of the [DIE.InjectableFactory] to +// +// [Provider]: [github.com/IBM/fp-go/di/erasure.Provider] package di //go:generate go run .. di --count 10 --filename gen.go diff --git a/di/erasure/injector.go b/di/erasure/injector.go index ed7e273..f7b26b9 100644 --- a/di/erasure/injector.go +++ b/di/erasure/injector.go @@ -115,6 +115,9 @@ func itemProviderFactory(fcts []ProviderFactory) ProviderFactory { } // MakeInjector creates an [InjectableFactory] based on a set of [Provider]s +// +// The resulting [InjectableFactory] can then be used to retrieve service instances given their [Dependency]. The implementation +// makes sure to transitively resolve the required dependencies. func MakeInjector(providers []Provider) InjectableFactory { type Result = IOE.IOEither[error, any] diff --git a/di/erasure/provider.go b/di/erasure/provider.go index 1a07fa5..d7f1967 100644 --- a/di/erasure/provider.go +++ b/di/erasure/provider.go @@ -32,6 +32,7 @@ import ( ) type ( + // InjectableFactory is a factory function that can create an untyped instance of a service based on its [Dependency] identifier InjectableFactory = func(Dependency) IOE.IOEither[error, any] ProviderFactory = func(InjectableFactory) IOE.IOEither[error, any] @@ -83,6 +84,8 @@ var ( mergeMaps = R.UnionLastMonoid[int, any]() collectParams = R.CollectOrd[any, any](Int.Ord)(F.SK[int, any]) + mapDeps = F.Curry2(A.MonadMap[Dependency, IOE.IOEither[error, any]]) + handlers = map[int]handler{ Identity: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] { return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] { @@ -171,7 +174,7 @@ func MakeProviderFactory( fct func(param ...any) IOE.IOEither[error, any]) ProviderFactory { return F.Flow3( - F.Curry2(A.MonadMap[Dependency, IOE.IOEither[error, any]])(deps), + mapDeps(deps), handleMapping(foldDeps(deps)), IOE.Chain(F.Unvariadic0(fct)), ) diff --git a/di/provider.go b/di/provider.go index a1cb1af..f88abb1 100644 --- a/di/provider.go +++ b/di/provider.go @@ -39,17 +39,17 @@ func eraseTuple[A, R any](f func(A) IOE.IOEither[error, R]) func(E.Either[error, ) } -func eraseProviderFactory0[R any](f func() IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { +func eraseProviderFactory0[R any](f IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { return func(params ...any) IOE.IOEither[error, any] { return F.Pipe1( - f(), + f, IOE.Map[error](F.ToAny[R]), ) } } func MakeProviderFactory0[R any]( - fct func() IOE.IOEither[error, R], + fct IOE.IOEither[error, R], ) DIE.ProviderFactory { return DIE.MakeProviderFactory( A.Empty[DIE.Dependency](), @@ -57,14 +57,14 @@ func MakeProviderFactory0[R any]( ) } -// MakeTokenWithDefault0 create a unique `InjectionToken` for a specific type with an attached default provider -func MakeTokenWithDefault0[R any](name string, fct func() IOE.IOEither[error, R]) InjectionToken[R] { +// MakeTokenWithDefault0 creates a unique [InjectionToken] for a specific type with an attached default [DIE.Provider] +func MakeTokenWithDefault0[R any](name string, fct IOE.IOEither[error, R]) InjectionToken[R] { return MakeTokenWithDefault[R](name, MakeProviderFactory0(fct)) } func MakeProvider0[R any]( token InjectionToken[R], - fct func() IOE.IOEither[error, R], + fct IOE.IOEither[error, R], ) DIE.Provider { return DIE.MakeProvider( token, @@ -74,5 +74,5 @@ func MakeProvider0[R any]( // ConstProvider simple implementation for a provider with a constant value func ConstProvider[R any](token InjectionToken[R], value R) DIE.Provider { - return MakeProvider0[R](token, F.Constant(IOE.Of[error](value))) + return MakeProvider0[R](token, IOE.Of[error](value)) } diff --git a/di/provider_test.go b/di/provider_test.go index 969237e..d950ddc 100644 --- a/di/provider_test.go +++ b/di/provider_test.go @@ -38,12 +38,10 @@ func TestSimpleProvider(t *testing.T) { var staticCount int - staticValue := func(value string) func() IOE.IOEither[error, string] { - return func() IOE.IOEither[error, string] { - return func() E.Either[error, string] { - staticCount++ - return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now())) - } + staticValue := func(value string) IOE.IOEither[error, string] { + return func() E.Either[error, string] { + staticCount++ + return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now())) } } @@ -82,12 +80,10 @@ func TestOptionalProvider(t *testing.T) { var staticCount int - staticValue := func(value string) func() IOE.IOEither[error, string] { - return func() IOE.IOEither[error, string] { - return func() E.Either[error, string] { - staticCount++ - return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now())) - } + staticValue := func(value string) IOE.IOEither[error, string] { + return func() E.Either[error, string] { + staticCount++ + return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now())) } } @@ -182,12 +178,10 @@ func TestEagerAndLazyProvider(t *testing.T) { var staticCount int - staticValue := func(value string) func() IOE.IOEither[error, string] { - return func() IOE.IOEither[error, string] { - return func() E.Either[error, string] { - staticCount++ - return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now())) - } + staticValue := func(value string) IOE.IOEither[error, string] { + return func() E.Either[error, string] { + staticCount++ + return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now())) } } @@ -307,7 +301,7 @@ func TestTokenWithDefaultProvider(t *testing.T) { // token without a default injToken1 := MakeToken[string]("Token1") // token with a default - injToken2 := MakeTokenWithDefault0("Token2", F.Constant(IOE.Of[error]("Carsten"))) + injToken2 := MakeTokenWithDefault0("Token2", IOE.Of[error]("Carsten")) // dependency injToken3 := MakeToken[string]("Token3") @@ -330,7 +324,7 @@ func TestTokenWithDefaultProvider(t *testing.T) { func TestTokenWithDefaultProviderAndOverride(t *testing.T) { // token with a default - injToken2 := MakeTokenWithDefault0("Token2", F.Constant(IOE.Of[error]("Carsten"))) + injToken2 := MakeTokenWithDefault0("Token2", IOE.Of[error]("Carsten")) // dependency injToken3 := MakeToken[string]("Token3") diff --git a/di/token.go b/di/token.go index 2208e4e..75ad819 100644 --- a/di/token.go +++ b/di/token.go @@ -42,20 +42,21 @@ type InjectionToken[T any] interface { // Identity idenifies this dependency as a mandatory, required dependency, it will be resolved eagerly and injected as `T`. // If the dependency cannot be resolved, the resolution process fails Identity() Dependency[T] - // Option identifies this dependency as optional, it will be resolved eagerly and injected as `O.Option[T]`. - // If the dependency cannot be resolved, the resolution process continues and the dependency is represented as `O.None[T]` + // Option identifies this dependency as optional, it will be resolved eagerly and injected as [O.Option[T]]. + // If the dependency cannot be resolved, the resolution process continues and the dependency is represented as [O.None[T]] Option() Dependency[O.Option[T]] - // IOEither identifies this dependency as mandatory but it will be resolved lazily as a `IOE.IOEither[error, T]`. This + // IOEither identifies this dependency as mandatory but it will be resolved lazily as a [IOE.IOEither[error, T]]. This // value is memoized to make sure the dependency is a singleton. // If the dependency cannot be resolved, the resolution process fails IOEither() Dependency[IOE.IOEither[error, T]] - // IOOption identifies this dependency as optional but it will be resolved lazily as a `IOO.IOOption[T]`. This + // IOOption identifies this dependency as optional but it will be resolved lazily as a [IOO.IOOption[T]]. This // value is memoized to make sure the dependency is a singleton. // If the dependency cannot be resolved, the resolution process continues and the dependency is represented as the none value. IOOption() Dependency[IOO.IOOption[T]] } -// MultiInjectionToken uniquely identifies a dependency by giving it an Id, Type and name. +// MultiInjectionToken uniquely identifies a dependency by giving it an Id, Type and name that can have multiple implementations. +// Implementations are provided via the [MultiInjectionToken.Item] injection token. type MultiInjectionToken[T any] interface { // Container returns the injection token used to request an array of all provided items Container() InjectionToken[[]T] @@ -146,7 +147,7 @@ func (m *multiInjectionToken[T]) Item() InjectionToken[T] { return m.item } -// makeToken create a unique `InjectionToken` for a specific type +// makeToken create a unique [InjectionToken] for a specific type func makeInjectionToken[T any](name string, providerFactory O.Option[DIE.ProviderFactory]) InjectionToken[T] { id := genId() toIdentity := toType[T]() @@ -158,12 +159,12 @@ func makeInjectionToken[T any](name string, providerFactory O.Option[DIE.Provide } } -// MakeToken create a unique `InjectionToken` for a specific type +// MakeToken create a unique [InjectionToken] for a specific type func MakeToken[T any](name string) InjectionToken[T] { return makeInjectionToken[T](name, O.None[DIE.ProviderFactory]()) } -// MakeToken create a unique `InjectionToken` for a specific type +// MakeToken create a unique [InjectionToken] for a specific type func MakeTokenWithDefault[T any](name string, providerFactory DIE.ProviderFactory) InjectionToken[T] { return makeInjectionToken[T](name, O.Of(providerFactory)) } diff --git a/ioeither/http/di/di.go b/ioeither/http/di/di.go index 2e1f045..657c6cd 100644 --- a/ioeither/http/di/di.go +++ b/ioeither/http/di/di.go @@ -21,13 +21,12 @@ import ( DI "github.com/IBM/fp-go/di" IOE "github.com/IBM/fp-go/ioeither" IOEH "github.com/IBM/fp-go/ioeither/http" - L "github.com/IBM/fp-go/lazy" ) var ( - // InjHttpClient is the injection token for the default http client - InjHttpClient = DI.MakeTokenWithDefault0("HTTP_CLIENT", L.Of(IOE.Of[error](http.DefaultClient))) + // InjHttpClient is the [DI.InjectionToken] for the [http.DefaultClient] + InjHttpClient = DI.MakeTokenWithDefault0("HTTP_CLIENT", IOE.Of[error](http.DefaultClient)) - // InjClient is the injection token for the default [Client] + // InjClient is the [DI.InjectionToken] for the default [IOEH.Client] InjClient = DI.MakeTokenWithDefault1("CLIENT", InjHttpClient.IOEither(), IOE.Map[error](IOEH.MakeClient)) )