ProductPromotion
Logo

Go.Lang

made by https://0x3d.site

GitHub - firasdarwish/ore: Lightweight, generic & simple dependency injection (DI) container for Go
Lightweight, generic & simple dependency injection (DI) container for Go - firasdarwish/ore
Visit Site

GitHub - firasdarwish/ore: Lightweight, generic & simple dependency injection (DI) container for Go

GitHub - firasdarwish/ore: Lightweight, generic & simple dependency injection (DI) container for Go

ore - Generic Dependency Injection Container for Go

Go Report Card Go Reference Mentioned in Awesome Go Maintainability codecov

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.

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 &simpleCounter{}, ctx
}

Eager Singleton

package main

import (
  "context"
  "github.com/firasdarwish/ore"
)

func main() {
  var c Counter
  c = &simpleCounter{}

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

  ctx := context.Background()

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

Lazy (using Creator[T] interface)

package main

import (
  "context"
  "fmt"
  "github.com/firasdarwish/ore"
)

func main() {
  // register
  ore.RegisterLazyCreator[Counter](ore.Scoped, &simpleCounter{})

  // OR
  //ore.RegisterLazyCreator[Counter](ore.Transient, &simpleCounter{})
  //ore.RegisterLazyCreator[Counter](ore.Singleton, &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)

package main

import (
  "context"
  "fmt"
  "github.com/firasdarwish/ore"
)

func main() {
  // register
  ore.RegisterLazyFunc[Counter](ore.Scoped, func(ctx context.Context) (Counter, context.Context) {
    return &simpleCounter{}, ctx
  })

  // OR
  //ore.RegisterLazyFunc[Counter](ore.Transient, func(ctx context.Context) (Counter, context.Context) {
  //  return &simpleCounter{}, ctx
  //})

  // Keyed service registration
  //ore.RegisterLazyFunc[Counter](ore.Singleton, func(ctx context.Context) (Counter, context.Context) {
  // return &simpleCounter{}, ctx
  //}, "name here", 1234)

  ctx := context.Background()

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

  // Keyed service retrieval
  //c, ctx := ore.Get[Counter](ctx, "name here", 1234)

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

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

Several Implementations

package main

import (
  "context"
  "github.com/firasdarwish/ore"
)

func main() {
  // register
  ore.RegisterLazyCreator[Counter](ore.Scoped, &simpleCounter{})

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

  ore.RegisterLazyFunc[Counter](ore.Transient, func(ctx context.Context) (Counter, context.Context) {
    return &simpleCounter{}, ctx
  })

  ore.RegisterLazyCreator[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

package main

import (
  "context"
  "fmt"
  "github.com/firasdarwish/ore"
)

func main() {
  // register
  ore.RegisterLazyFunc[Counter](ore.Singleton, func(ctx context.Context) (Counter, context.Context) {
    return &simpleCounter{}, ctx
  }, "name here", 1234)

  //ore.RegisterLazyCreator[Counter](ore.Scoped, &simpleCounter{}, "name here", 1234)

  //ore.RegisterEagerSingleton[Counter](&simpleCounter{}, "name here", 1234)

  ctx := context.Background()

  // Keyed service retrieval
  c, ctx := ore.Get[Counter](ctx, "name here", 1234)
  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.RegisterLazyFunc(ore.Scoped, func(ctx context.Context) (*Broker, context.Context) {
    return &Broker{Name: "Peter"}, ctx
  })
  ore.RegisterLazyFunc(ore.Scoped, func(ctx context.Context) (*Broker, context.Context) {
    return &Broker{Name: "John"}, ctx
  })
  ore.RegisterLazyFunc(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.

Graceful application termination

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

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.RegisterEagerSingleton(&Logger{}) //*Logger implements Shutdowner
ore.RegisterEagerSingleton(&SomeRepository{}) //*SomeRepository implements Shutdowner
ore.RegisterEagerSingleton(&SomeService{}, "some_module") //*SomeService implements Shutdowner

//On application termination, Ore can help to retreive all the singletons implementation of the `Shutdowner` interface.
//There might be other `Shutdowner`'s implementation which were lazily registered but have never been created (a.k.a invoked).
//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 created 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" one will be returned.
  • The returned instances are sorted by creation time (a.k.a the invocation order), the first one being the most recently created one.

Graceful context termination

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

Here how Ore can help you:

//Assuming that your Application provides certain instances with Scoped lifetime.
//Some of them implements a "Disposer" interface (defined winthin the application).
type Disposer interface {
  Dispose()
}
ore.RegisterLazyCreator(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 created 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 life time.
  • All the implementations including "keyed" one will be returned.
  • The returned instances are sorted by creation time (a.k.a the invocation order), the first one being the most recently created one.

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
}
package main

import (
  "context"
  "github.com/firasdarwish/ore"
)

func main() {

  // register
  ore.RegisterLazyFunc[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
BenchmarkRegisterLazyFunc
BenchmarkRegisterLazyFunc-20             4953412               233.5 ns/op
BenchmarkRegisterLazyCreator
BenchmarkRegisterLazyCreator-20          5468863               231.3 ns/op
BenchmarkRegisterEagerSingleton
BenchmarkRegisterEagerSingleton-20       4634733               267.4 ns/op
BenchmarkGet
BenchmarkGet-20                          3766730               321.9 ns/op
BenchmarkGetList
BenchmarkGetList-20                      1852132               637.0 ns/op

๐Ÿ‘ค 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.

Articles
to learn more about the golang concepts.

Resources
which are currently available to browse on.

mail [email protected] to add your project or resources here ๐Ÿ”ฅ.

FAQ's
to know more about the topic.

mail [email protected] to add your project or resources here ๐Ÿ”ฅ.

Queries
or most google FAQ's about GoLang.

mail [email protected] to add more queries here ๐Ÿ”.

More Sites
to check out once you're finished browsing here.

0x3d
https://www.0x3d.site/
0x3d is designed for aggregating information.
NodeJS
https://nodejs.0x3d.site/
NodeJS Online Directory
Cross Platform
https://cross-platform.0x3d.site/
Cross Platform Online Directory
Open Source
https://open-source.0x3d.site/
Open Source Online Directory
Analytics
https://analytics.0x3d.site/
Analytics Online Directory
JavaScript
https://javascript.0x3d.site/
JavaScript Online Directory
GoLang
https://golang.0x3d.site/
GoLang Online Directory
Python
https://python.0x3d.site/
Python Online Directory
Swift
https://swift.0x3d.site/
Swift Online Directory
Rust
https://rust.0x3d.site/
Rust Online Directory
Scala
https://scala.0x3d.site/
Scala Online Directory
Ruby
https://ruby.0x3d.site/
Ruby Online Directory
Clojure
https://clojure.0x3d.site/
Clojure Online Directory
Elixir
https://elixir.0x3d.site/
Elixir Online Directory
Elm
https://elm.0x3d.site/
Elm Online Directory
Lua
https://lua.0x3d.site/
Lua Online Directory
C Programming
https://c-programming.0x3d.site/
C Programming Online Directory
C++ Programming
https://cpp-programming.0x3d.site/
C++ Programming Online Directory
R Programming
https://r-programming.0x3d.site/
R Programming Online Directory
Perl
https://perl.0x3d.site/
Perl Online Directory
Java
https://java.0x3d.site/
Java Online Directory
Kotlin
https://kotlin.0x3d.site/
Kotlin Online Directory
PHP
https://php.0x3d.site/
PHP Online Directory
React JS
https://react.0x3d.site/
React JS Online Directory
Angular
https://angular.0x3d.site/
Angular JS Online Directory