ore

package module
v0.6.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 16, 2024 License: MIT Imports: 10 Imported by: 0

README ¶

ore - Generic Dependency Injection Container for Go

Go Report Card Go Reference Mentioned in Awesome Go Maintainability codecov FOSSA Status FOSSA Status ore


ore is a lightweight, generic & simple dependency injection (DI) container for Go.

Inspired by the principles of ASP.NET Dependency Injection, designed to facilitate the management of object lifetimes and the inversion of control in your applications.


Features

  • Singletons: Register components as singletons, ensuring that there's only one instance throughout the entire application.

  • Transients: Register components as transients, creating a new instance each time it is requested.

  • Scoped Instances: Register components as scoped, tying them to a specific context or scope. Scoped components are created once per scope and reused within that scope.

  • Lazy Initialization: Support for lazy initialization of components, allowing for efficient resource utilization.

  • Multiple Implementations of the Same Interface: Register and retrieve several implementations of the same interface type, allowing for flexible and modular design.

  • Keyed Services Injection: Support for injecting services based on a key, allowing you to differentiate between multiple implementations of the same interface or type.

  • Concurrency-Safe: Utilizes a mutex to ensure safe concurrent access to the container.

  • Placeholder Service Registration: Designed to simplify scenarios where certain dependencies cannot be resolved at the time of service registration but can be resolved later during runtime.

  • Isolated Containers: Enables the creation of multiple isolated, modular containers to support more scalable and testable architectures, especially for Modular Monoliths.

  • Aliases: A powerful way to register type mappings, allowing one type to be treated as another, typically an interface or a more abstract type.

  • Runtime Validation: Allow for better error handling and validation during the startup or testing phases of an application (circular dependencies, lifetime misalignment, and missing dependencies).

  • Graceful Termination: To ensure graceful application (or context) termination and proper cleanup of resources, including shutting down all resolved objects created during the application (or context) lifetime.

Installation

go get -u github.com/firasdarwish/ore

Usage

Import
import "github.com/firasdarwish/ore"
Example Service
// interface
type Counter interface {
  AddOne()
  GetCount() int
}

// implementation
type simpleCounter struct {
  counter int
}

func (c *simpleCounter) AddOne()  {
  c.counter++
}

func (c *simpleCounter) GetCount() int {
  return c.counter
}

func (c *simpleCounter) New(ctx context.Context) (Counter, context.Context) {
  return &models.SimpleCounter{}, ctx
}

Eager Singleton
var c Counter
c = &models.SimpleCounter{}

// register
ore.RegisterSingleton[Counter](c)

ctx := context.Background()

// retrieve
c, ctx := ore.Get[Counter](ctx)
c.AddOne()
c.AddOne()

Lazy (using Creator[T] interface)
// register
ore.RegisterCreator[Counter](ore.Scoped, &models.SimpleCounter{})

// OR
//ore.RegisterCreator[Counter](ore.Transient, &models.SimpleCounter{})
//ore.RegisterCreator[Counter](ore.Singleton, &models.SimpleCounter{})

ctx := context.Background()

// retrieve
c, ctx := ore.Get[Counter](ctx)
c.AddOne()
c.AddOne()

// retrieve again
c, ctx = ore.Get[Counter](ctx)
c.AddOne()

// prints out: `TOTAL: 3`
fmt.Println("TOTAL: ", c.GetCount())

Lazy (using anonymous func)
  // register
ore.RegisterFunc[Counter](ore.Scoped, func(ctx context.Context) (Counter, context.Context) {
    return &models.SimpleCounter{}, ctx
})

// OR
//ore.RegisterFunc[Counter](ore.Transient, func(ctx context.Context) (Counter, context.Context) {
//  return &models.SimpleCounter{}, ctx
//})

// Keyed service registration
//ore.RegisterKeyedFunc[Counter](ore.Singleton, func(ctx context.Context) (Counter, context.Context) {
// return &models.SimpleCounter{}, ctx
//}, "key-here")

ctx := context.Background()

// retrieve
c, ctx := ore.Get[Counter](ctx)

c.AddOne()
c.AddOne()

// Keyed service retrieval
//c, ctx := ore.GetKeyed[Counter](ctx, "key-here")

// retrieve again
c, ctx = ore.Get[Counter](ctx)
c.AddOne()

// prints out: `TOTAL: 3`
fmt.Println("TOTAL: ", c.GetCount())

Several Implementations
  // register
ore.RegisterCreator[Counter](ore.Scoped, &models.SimpleCounter{})

ore.RegisterCreator[Counter](ore.Scoped, &yetAnotherCounter{})

ore.RegisterFunc[Counter](ore.Transient, func(ctx context.Context) (Counter, context.Context) {
    return &models.SimpleCounter{}, ctx
})

ore.RegisterCreator[Counter](ore.Singleton, &yetAnotherCounter{})

ctx := context.Background()

// returns a slice of `Counter` implementations
counters, ctx := ore.GetList[Counter](ctx)

// to retrieve a slice of keyed services
//counters, ctx := ore.GetList[Counter](ctx, "my integer counters")

for _, c := range counters {
    c.AddOne()
}

// It will always return the LAST registered implementation
defaultImplementation, ctx := ore.Get[Counter](ctx) // simpleCounter
defaultImplementation.AddOne()
Injecting Mocks in Tests

The last registered implementation takes precedence, so you can register a mock implementation in the test, which will override the real implementation.


Keyed Services Retrieval Example
  // register
ore.RegisterKeyedFunc[Counter](ore.Singleton, func(ctx context.Context) (Counter, context.Context) {
    return &models.SimpleCounter{}, ctx
}, "key-here")

//ore.RegisterKeyedCreator[Counter](ore.Scoped, &models.SimpleCounter{}, "key-here")

//ore.RegisterKeyedSingleton[Counter](&models.SimpleCounter{}, "key-here")

ctx := context.Background()

// Keyed service retrieval
c, ctx := ore.GetKeyed[Counter](ctx, "key-here")
c.AddOne()

// prints out: `TOTAL: 1`
fmt.Println("TOTAL: ", c.GetCount())

Alias: Register struct, Get interface
type IPerson interface{}
type Broker struct {
  Name string
} //implements IPerson

type Trader struct {
  Name string
} //implements IPerson

func TestGetInterfaceAlias(t *testing.T) {
  ore.RegisterFunc(ore.Scoped, func(ctx context.Context) (*Broker, context.Context) {
    return &Broker{Name: "Peter"}, ctx
  })
  ore.RegisterFunc(ore.Scoped, func(ctx context.Context) (*Broker, context.Context) {
    return &Broker{Name: "John"}, ctx
  })
  ore.RegisterFunc(ore.Scoped, func(ctx context.Context) (*Trader, context.Context) {
    return &Trader{Name: "Mary"}, ctx
  })

  ore.RegisterAlias[IPerson, *Trader]() //link IPerson to *Trader
  ore.RegisterAlias[IPerson, *Broker]() //link IPerson to *Broker

  //no IPerson was registered to the container, but we can still `Get` it out of the container.
  //(1) IPerson is alias to both *Broker and *Trader. *Broker takes precedence because it's the last one linked to IPerson.
  //(2) multiple *Borker (Peter and John) are registered to the container, the last registered (John) takes precedence.
  person, _ := ore.Get[IPerson](context.Background()) // will return the broker John

  personList, _ := ore.GetList[IPerson](context.Background()) // will return all registered broker and trader
}

Alias is also scoped by key. When you "Get" an alias with keys for eg: ore.Get[IPerson](ctx, "module1") then Ore would return only Services registered under this key ("module1") and panic if no service found.


Registration Validation

Once you're done with registering all the services, it is recommended to call ore.Seal(), then ore.Validate(), then finally ore.DefaultContainer.DisableValidation=true.

ore.Validate() invokes ALL your registered resolvers. The purpose is to panic early if your registrations were in bad shape:

  • Missing Dependency: you forgot to register certain resolvers.
  • Circular Dependency: A depends on B which depends on A.
  • Lifetime Misalignment: a longer lifetime service (eg. Singleton) depends on a shorter one (eg Transient).

Registration Recommendation

(1) You should call ore.Validate()

  • In a test which is automatically run on your CI/CD pipeline (option 1)
  • On application start, right after all the registrations (option 2)

Option 1 (run ore.Validate on test) is usually a better choice.

(2) It is recommended to seal your container ore.Seal() (which seals the container) on application start => Please don't call ore.RegisterXX all over the place.

(3) A combination of ore.Buile() and then ore.Validate() and then ore.DefaultContainer.DisabledValidation=true ensures no more new resolvers will be registered AND all registered resolvers are validated, this will prevent any further validation each time a resolver is invoked (ore.Get) which greatly enhances performance.

(4) Keep the object creation function (a.k.a resolvers) simple. Their only responsibility should be object creation.

  • They should not spawn new goroutine
  • They should not open database connection
  • They should not contain any "if" statement or other business logic

Graceful application termination

On application termination, you want to call Shutdown() on all the "Singletons" objects which have been created during the application lifetime.

Here how Ore can help you:

// Assuming that the Application provides certain instances with Singleton lifetime.
// Some of these singletons implement a custom `Shutdowner` interface (defined within the application)
type Shutdowner interface {
  Shutdown()
}
ore.RegisterSingleton(&Logger{}) //*Logger implements Shutdowner
ore.RegisterSingleton(&SomeRepository{}) //*SomeRepository implements Shutdowner
ore.RegisterKeyedSingleton(&SomeService{}, "some_module") //*SomeService implements Shutdowner

//On application termination, Ore can help to retrieve all the singletons implementation 
//of the `Shutdowner` interface.
//There might be other `Shutdowner`'s implementation which were lazily registered but 
//have never been created.
//Ore will ignore them, and return only the concrete instances which can be Shutdown()
shutdowables := ore.GetResolvedSingletons[Shutdowner]() 

//Now we can Shutdown() them all and gracefully terminate our application.
//The most recently invoked instance will be Shutdown() first
for _, instance := range disposables {
   instance.Shutdown()
}

In resume, the ore.GetResolvedSingletons[TInterface]() function returns a list of Singleton implementations of the [TInterface].

  • It returns only the instances which had been invoked (a.k.a resolved).
  • All the implementations (including "keyed" ones) will be returned.
  • The returned instances are sorted by the invocation order, the first one being latest invoked one.
    • if "A" depends on "B", "C", Ore will make sure to return "B" and "C" first in the list so that they would be shutdowned before "A".

Graceful context termination

On context termination, you want to call Dispose() on all the "Scoped" objects which have been created during the context lifetime.

Here how Ore can help you:

//Assuming that your Application provides certain instances with Scoped lifetime.
//Some of them implements a "Disposer" interface (defined within the application).
type Disposer interface {
  Dispose()
}
ore.RegisterCreator(ore.Scoped, &SomeDisposableService{}) //*SomeDisposableService implements Disposer

//a new request arrive
ctx, cancel := context.WithCancel(context.Background())

//start a go routine that will clean up resources when the context is canceled
go func() {
  <-ctx.Done() // Wait for the context to be canceled
  // Perform your cleanup tasks here
  disposables := ore.GetResolvedScopedInstances[Disposer](ctx)
  //The most recently invoked instance will be Dispose() first
  for _, d := range disposables {
    _ = d.Dispose(ctx)
  }
}()
...
ore.Get[*SomeDisposableService](ctx) //invoke some scoped services
cancel() //cancel the ctx

The ore.GetResolvedScopedInstances[TInterface](context) function returns a list of implementations of the [TInterface] which are Scoped in the input context:

  • It returns only the instances which had been invoked (a.k.a resolved) during the context lifetime.
  • All the implementations (of all modules) including "keyed" one will be returned.
  • The returned instances are sorted by invocation order, the first one being the latest invoked one.
    • if "A" depends on "B", "C", Ore will make sure to return "B" and "C" first in the list so that they would be Disposed before "A".

Multiple Containers (a.k.a Modules)
DefaultContainer Custom container
Get GetFromContainer
GetList GetListFromContainer
GetResolvedSingletons GetResolvedSingletonsFromContainer
RegisterAlias RegisterAliasToContainer
RegisterSingleton RegisterSingletonToContainer
RegisterCreator RegisterCreatorToContainer
RegisterFunc RegisterFuncToContainer
RegisterPlaceholder RegisterPlaceholderToContainer
ProvideScopedValue ProvideScopedValueToContainer

Most of time you only need the Default Container. In rare use case such as the Modular Monolith Architecture, you might want to use multiple containers, one per module. Ore provides minimum support for "module" in this case:

//broker module
brokerContainer := ore.NewContainer()
ore.RegisterFuncToContainer(brokerContainer, ore.Singleton, func(ctx context.Context) (*Broker, context.Context) {
  brs, ctx = ore.GetFromContainer[*BrokerageSystem](brokerContainer, ctx)
  return &Broker{brs}, ctx
})
// brokerContainer.Seal() //prevent further registration
// brokerContainer.Validate() //check the dependency graph
// brokerContainer.DisableValidation = true //disable check when resolve new object
broker, _ := ore.GetFromContainer[*Broker](brokerContainer, context.Background())

//trader module
traderContainer := ore.NewContainer()
ore.RegisterFuncToContainer(traderContainer, ore.Singleton, func(ctx context.Context) (*Trader, context.Context) {
  mkp, ctx = ore.GetFromContainer[*MarketPlace](traderContainer, ctx)
  return &Trader{mkp}, ctx
})
trader, _ := ore.GetFromContainer[*Trader](traderContainer, context.Background())

Important: You will have to prevent cross modules access to the containers by yourself. For eg, don't let your "Broker module" to have access to the traderContainer of the "Trader module".


Injecting value at Runtime

A common scenario is that your "Service" depends on something which you couldn't provide on registration time. You can provide this dependency only when certain requests or events arrive later. Ore allows you to build an "incomplete" dependency graph using the "placeholder".

//register SomeService which depends on "someConfig"
ore.RegisterFunc[*SomeService](ore.Scoped, func(ctx context.Context) (*SomeService, context.Context) {
  someConfig, ctx := ore.GetKeyed[string](ctx, "someConfig")
  return &SomeService{someConfig}, ctx
})

//someConfig is unknow at registration time because 
//this value depends on the future user's request
ore.RegisterKeyedPlaceholder[string]("someConfig")

//a new request arrive
ctx := context.Background()
//suppose that the request is sent by "admin"
ctx = context.WithValue(ctx, "role", "admin")

//inject a different somConfig value depending on the request's content
userRole := ctx.Value("role").(string)
if userRole == "admin" {
  ctx = ore.ProvideKeyedScopedValue(ctx, "Admin config", "someConfig")
} else if userRole == "supervisor" {
  ctx = ore.ProvideKeyedScopedValue(ctx, "Supervisor config", "someConfig")
} else if userRole == "user" {
  if (isAuthenticatedUser) {
    ctx = ore.ProvideKeyedScopedValue(ctx, "Private user config", "someConfig")
  } else {
    ctx = ore.ProvideKeyedScopedValue(ctx, "Public user config", "someConfig")
  }
}

//Get the service to handle this request
service, ctx := ore.Get[*SomeService](ctx)
fmt.Println(service.someConfig) //"Admin config"

(See full codes here)

  • ore.RegisterPlaceholder[T](key...) registers a future value with Scoped lifetime.

    • This value will be injected in runtime using the ProvideScopedValue function.
    • Resolving objects which depend on this value will panic if the value has not been provided.
  • ore.ProvideScopedValue[T](context, value T, key...) injects a concrete value into the given context

    • ore can access (Get() or GetList()) to this value only if the corresponding placeholder (which matches the type and keys) is registered.
  • A value provided to a placeholder would never replace value returned by other resolvers. It's the opposite, if a type (and key) could be resolved by a real resolver (such as RegisterFunc, RegisterCreator...), then the later would take precedent.


More Complex Example


type Numeric interface {
  int
}

type GenericCounter[T Numeric] interface {
  Add(number T)
  GetCount() T
}

type genericCounter[T Numeric] struct {
  counter T
}

func (gc *genericCounter[T]) Add(number T) {
  gc.counter += number
}

func (gc *genericCounter[T]) GetCount(ctx context.Context) T {
  return gc.counter
}

// register
ore.RegisterFunc[GenericCounter[int]](ore.Scoped, func(ctx context.Context) (GenericCounter[int], context.Context) {
    return &genericCounter[int]{}, ctx
})

// retrieve
c, ctx := ore.Get[GenericCounter[int]](ctx)

Benchmarks

goos: windows
goarch: amd64
pkg: github.com/firasdarwish/ore
cpu: 13th Gen Intel(R) Core(TM) i9-13900H
BenchmarkRegisterFunc-20                 5612482               214.6 ns/op
BenchmarkRegisterCreator-20              6498038               174.1 ns/op
BenchmarkRegisterSingleton-20            5474991               259.1 ns/op
BenchmarkInitialGet-20                   2297595               514.3 ns/op
BenchmarkGet-20                          9389530               122.1 ns/op
BenchmarkInitialGetList-20               1000000               1072 ns/op
BenchmarkGetList-20                      3970850               301.7 ns/op
PASS
ok      github.com/firasdarwish/ore     10.883s

Checkout also examples/benchperf/README.md


👤 Contributors

Contributors

Contributing

Feel free to contribute by opening issues, suggesting features, or submitting pull requests. We welcome your feedback and contributions.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation ¶

Index ¶

Constants ¶

This section is empty.

Variables ¶

View Source
var (
	DefaultContainer = NewContainer()
)

Functions ¶

func ContainerID ¶ added in v0.5.1

func ContainerID() int32

func Get ¶

func Get[T any](ctx context.Context) (T, context.Context)

Get Retrieves an instance based on type and key (panics if no valid implementations)

func GetFromContainer ¶ added in v0.5.0

func GetFromContainer[T any](con *Container, ctx context.Context) (T, context.Context)

GetFromContainer Retrieves an instance from the given container based on type and key (panics if no valid implementations)

func GetKeyed ¶ added in v0.6.0

func GetKeyed[T any, K comparable](ctx context.Context, key K) (T, context.Context)

GetKeyed Retrieves an instance based on type and key (panics if no valid implementations)

func GetKeyedFromContainer ¶ added in v0.6.0

func GetKeyedFromContainer[T any, K comparable](con *Container, ctx context.Context, key K) (T, context.Context)

GetKeyedFromContainer Retrieves an instance from the given container based on type and key (panics if no valid implementations)

func GetKeyedList ¶ added in v0.6.0

func GetKeyedList[T any, K comparable](ctx context.Context, key K) ([]T, context.Context)

GetKeyedList Retrieves a list of instances based on type and key

func GetKeyedListFromContainer ¶ added in v0.6.0

func GetKeyedListFromContainer[T any, K comparable](con *Container, ctx context.Context, key K) ([]T, context.Context)

GetKeyedListFromContainer Retrieves a list of instances from the given container based on type and key

func GetList ¶

func GetList[T any](ctx context.Context) ([]T, context.Context)

GetList Retrieves a list of instances based on type and key

func GetListFromContainer ¶ added in v0.5.0

func GetListFromContainer[T any](con *Container, ctx context.Context) ([]T, context.Context)

GetListFromContainer Retrieves a list of instances from the given container based on type and key

func GetResolvedScopedInstances ¶ added in v0.3.0

func GetResolvedScopedInstances[TInterface any](ctx context.Context) []TInterface

GetResolvedScopedInstances retrieves a list of Scoped instances that implement the [TInterface]. The returned instances are sorted by creation time (a.k.a the invocation order), the first one being the most recently created one. If an instance "A" depends on certain instances "B" and "C" then this function guarantee to return "B" and "C" before "A" in the list. It would return only the instances which had been resolved. Other lazy implementations which have never been invoked will not be returned. This function is useful for cleaning operations.

Example:

     disposableInstances := ore.GetResolvedScopedInstances[Disposer](ctx)
	 for _, disposable := range disposableInstances {
	   disposable.Dispose()
	 }

func GetResolvedSingletons ¶ added in v0.3.0

func GetResolvedSingletons[TInterface any]() []TInterface

GetResolvedSingletons retrieves a list of Singleton instances that implement the [TInterface]. The returned instances are sorted by creation time (a.k.a the invocation order), the first one being the "most recently" created one. If an instance "A" depends on certain instances "B" and "C" then this function guarantee to return "B" and "C" before "A" in the list. It would return only the instances which had been resolved. Other lazy implementations which have never been invoked will not be returned. This function is useful for cleaning operations.

Example:

     disposableSingletons := ore.GetResolvedSingletons[Disposer]()
	 for _, disposable := range disposableSingletons {
	   disposable.Dispose()
	 }

func GetResolvedSingletonsFromContainer ¶ added in v0.5.0

func GetResolvedSingletonsFromContainer[TInterface any](con *Container) []TInterface

GetResolvedSingletonsFromContainer retrieves a list of Singleton instances that implement the [TInterface] from the given container. See GetResolvedSingletons for more information.

func IsSealed ¶ added in v0.5.1

func IsSealed() bool

IsSealed checks whether the DEFAULT container is sealed (in readonly mode)

func Name ¶ added in v0.5.1

func Name() string

func ProvideKeyedScopedValue ¶ added in v0.6.0

func ProvideKeyedScopedValue[T any, K comparable](ctx context.Context, value T, key K) context.Context

ProvideKeyedScopedValue injects a concrete value into the given context. This value will be available only to the default container. And the container can only resolve this value if it has the matching (type and key's) Placeholder registered. Checkout the RegisterPlaceholder function for more info.

func ProvideKeyedScopedValueToContainer ¶ added in v0.6.0

func ProvideKeyedScopedValueToContainer[T any, K comparable](con *Container, ctx context.Context, value T, key K) context.Context

ProvideKeyedScopedValueToContainer injects a concrete value into the given context. This value will be available only to the given container. And the container can only resolve this value if it has the matching (type and key's) Placeholder registered. Checkout the RegisterPlaceholderToContainer function for more info.

func ProvideScopedValue ¶ added in v0.5.0

func ProvideScopedValue[T any](ctx context.Context, value T) context.Context

ProvideScopedValue injects a concrete value into the given context. This value will be available only to the default container. And the container can only resolve this value if it has the matching (type and key's) Placeholder registered. Checkout the RegisterPlaceholder function for more info.

func ProvideScopedValueToContainer ¶ added in v0.5.0

func ProvideScopedValueToContainer[T any](con *Container, ctx context.Context, value T) context.Context

ProvideScopedValueToContainer injects a concrete value into the given context. This value will be available only to the given container. And the container can only resolve this value if it has the matching (type and key's) Placeholder registered. Checkout the RegisterPlaceholderToContainer function for more info.

func RegisterAlias ¶ added in v0.3.0

func RegisterAlias[TInterface, TImpl any]()

RegisterAlias Registers an interface type to a concrete implementation. Allowing you to register the concrete implementation to the default container and later get the interface from it.

func RegisterAliasToContainer ¶ added in v0.5.0

func RegisterAliasToContainer[TInterface, TImpl any](con *Container)

RegisterAliasToContainer Registers an interface type to a concrete implementation in the given container. Allowing you to register the concrete implementation to the container and later get the interface from it.

func RegisterCreator ¶ added in v0.6.0

func RegisterCreator[T any](lifetime Lifetime, creator Creator[T])

RegisterCreator Registers a lazily initialized value using a `Creator[T]` interface

func RegisterCreatorToContainer ¶ added in v0.6.0

func RegisterCreatorToContainer[T any](con *Container, lifetime Lifetime, creator Creator[T])

RegisterCreatorToContainer Registers a lazily initialized value to the given container using a `Creator[T]` interface

func RegisterFunc ¶ added in v0.6.0

func RegisterFunc[T any](lifetime Lifetime, initializer Initializer[T])

RegisterFunc Registers a lazily initialized value using an `Initializer[T]` function signature

func RegisterFuncToContainer ¶ added in v0.6.0

func RegisterFuncToContainer[T any](con *Container, lifetime Lifetime, initializer Initializer[T])

RegisterFuncToContainer Registers a lazily initialized value to the given container using an `Initializer[T]` function signature

func RegisterKeyedCreator ¶ added in v0.6.0

func RegisterKeyedCreator[T any, K comparable](lifetime Lifetime, creator Creator[T], key K)

RegisterKeyedCreator Registers a lazily initialized value using a `Creator[T]` interface

func RegisterKeyedCreatorToContainer ¶ added in v0.6.0

func RegisterKeyedCreatorToContainer[T any, K comparable](con *Container, lifetime Lifetime, creator Creator[T], key K)

RegisterKeyedCreatorToContainer Registers a lazily initialized value to the given container using a `Creator[T]` interface

func RegisterKeyedFunc ¶ added in v0.6.0

func RegisterKeyedFunc[T any, K comparable](lifetime Lifetime, initializer Initializer[T], key K)

RegisterKeyedFunc Registers a lazily initialized value using an `Initializer[T]` function signature

func RegisterKeyedFuncToContainer ¶ added in v0.6.0

func RegisterKeyedFuncToContainer[T any, K comparable](con *Container, lifetime Lifetime, initializer Initializer[T], key K)

RegisterKeyedFuncToContainer Registers a lazily initialized value to the given container using an `Initializer[T]` function signature

func RegisterKeyedPlaceholder ¶ added in v0.6.0

func RegisterKeyedPlaceholder[T any, K comparable](key K)

RegisterKeyedPlaceholder registers a future value with Scoped lifetime. This value will be injected in runtime using the ProvideScopedValue function. Resolving objects which depend on this value will panic if the value has not been provided. Placeholder with the same type and key can be registered only once.

func RegisterKeyedPlaceholderToContainer ¶ added in v0.6.0

func RegisterKeyedPlaceholderToContainer[T any, K comparable](con *Container, key K)

RegisterKeyedPlaceholderToContainer registers a future value with Scoped lifetime to the given container. This value will be injected in runtime using the ProvideScopedValue function. Resolving objects which depend on this value will panic if the value has not been provided. Placeholder with the same type and key can be registered only once.

func RegisterKeyedSingleton ¶ added in v0.6.0

func RegisterKeyedSingleton[T any, K comparable](impl T, key K)

RegisterKeyedSingleton Registers an eagerly instantiated singleton value To register an eagerly instantiated scoped value use ProvideScopedValue

func RegisterKeyedSingletonToContainer ¶ added in v0.6.0

func RegisterKeyedSingletonToContainer[T any, K comparable](con *Container, impl T, key K)

RegisterKeyedSingletonToContainer Registers an eagerly instantiated singleton value to the given container. To register an eagerly instantiated scoped value use ProvideScopedValueToContainer

func RegisterPlaceholder ¶ added in v0.6.0

func RegisterPlaceholder[T any]()

RegisterPlaceholder registers a future value with Scoped lifetime. This value will be injected in runtime using the ProvideScopedValue function. Resolving objects which depend on this value will panic if the value has not been provided. Placeholder with the same type and key can be registered only once.

func RegisterPlaceholderToContainer ¶ added in v0.6.0

func RegisterPlaceholderToContainer[T any](con *Container)

RegisterPlaceholderToContainer registers a future value with Scoped lifetime to the given container. This value will be injected in runtime using the ProvideScopedValue function. Resolving objects which depend on this value will panic if the value has not been provided. Placeholder with the same type and key can be registered only once.

func RegisterSingleton ¶ added in v0.6.0

func RegisterSingleton[T any](impl T)

RegisterSingleton Registers an eagerly instantiated singleton value To register an eagerly instantiated scoped value use ProvideScopedValue

func RegisterSingletonToContainer ¶ added in v0.6.0

func RegisterSingletonToContainer[T any](con *Container, impl T)

RegisterSingletonToContainer Registers an eagerly instantiated singleton value to the given container. To register an eagerly instantiated scoped value use ProvideScopedValueToContainer

func Seal ¶ added in v0.5.1

func Seal()

Seal puts the DEFAULT container into read-only mode, preventing any further registrations.

func Validate ¶ added in v0.4.0

func Validate()

Validate invokes all registered resolvers. It panics if any of them fails. It is recommended to call this function on application start, or in the CI/CD test pipeline The objective is to panic early when the container is bad configured. For eg:

  • (1) Missing dependency (forget to register certain resolvers)
  • (2) cyclic dependency
  • (3) lifetime misalignment (a longer lifetime service depends on a shorter one).

Types ¶

type Container ¶ added in v0.5.0

type Container struct {
	//DisableValidation is false by default, Set to true to skip validation.
	// Use case: you called the [Validate] function (either in the test pipeline or on application startup).
	// So you are confident that your registrations are good:
	//
	//   - no missing dependencies
	//   - no circular dependencies
	//   - no lifetime misalignment (a longer lifetime service depends on a shorter one).
	//
	// You don't need Ore to validate over and over again each time it creates a new concrete.
	// It's a waste of resource especially when you will need Ore to create a million of transient concretes
	// and any "pico" seconds or memory allocation matter for you.
	//
	// In this case, you can set DisableValidation = true.
	//
	// This config would impact also the [GetResolvedSingletons] and the [GetResolvedScopedInstances] functions,
	// the returning order would be no longer guaranteed.
	DisableValidation bool
	// contains filtered or unexported fields
}

func NewContainer ¶ added in v0.5.0

func NewContainer() *Container

func (*Container) ContainerID ¶ added in v0.5.1

func (c *Container) ContainerID() int32

func (*Container) IsSealed ¶ added in v0.5.1

func (this *Container) IsSealed() bool

IsSealed checks whether the container is sealed (in readonly mode)

func (*Container) Name ¶ added in v0.5.1

func (c *Container) Name() string

func (*Container) Seal ¶ added in v0.5.1

func (this *Container) Seal()

Seal puts the container into read-only mode, preventing any further registrations.

func (*Container) SetName ¶ added in v0.5.1

func (this *Container) SetName(name string) *Container

func (*Container) Validate ¶ added in v0.5.0

func (this *Container) Validate()

Validate invokes all registered resolvers. It panics if any of them fails. It is recommended to call this function on application start, or in the CI/CD test pipeline The objective is to panic early when the container is bad configured. For eg:

  • (1) Missing dependency (forget to register certain resolvers)
  • (2) cyclic dependency
  • (3) lifetime misalignment (a longer lifetime service depends on a shorter one).

type Creator ¶

type Creator[T any] interface {
	New(ctx context.Context) (T, context.Context)
}

type Initializer ¶

type Initializer[T any] func(ctx context.Context) (T, context.Context)

type Lifetime ¶ added in v0.1.1

type Lifetime int
const (
	Transient Lifetime = 0
	Scoped    Lifetime = 1
	Singleton Lifetime = 2
)

The bigger the value, the longer the lifetime

func (Lifetime) String ¶ added in v0.4.0

func (this Lifetime) String() string

Directories ¶

Path Synopsis
internal
testtools/assert2
assert2 package add missing assertions from testify/assert package
assert2 package add missing assertions from testify/assert package

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL