Skip to content

Commit

Permalink
updated README.md and benchmarks
Browse files Browse the repository at this point in the history
  • Loading branch information
Firas Darwish committed Nov 15, 2024
1 parent 1011e30 commit d82a8d1
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 53 deletions.
89 changes: 52 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@ the management of object lifetimes and the inversion of control in your applicat

- **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.
<br />

## Installation
Expand Down Expand Up @@ -99,7 +113,7 @@ var c Counter
c = &models.SimpleCounter{}

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

ctx := context.Background()

Expand Down Expand Up @@ -152,19 +166,20 @@ ore.RegisterFunc[Counter](ore.Scoped, func(ctx context.Context) (Counter, contex
//})

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

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)
//c, ctx := ore.GetKeyed[Counter](ctx, "key-here")

// retrieve again
c, ctx = ore.Get[Counter](ctx)
Expand Down Expand Up @@ -217,18 +232,18 @@ The last registered implementation takes precedence, so you can register a mock

```go
// register
ore.RegisterFunc[Counter](ore.Singleton, func(ctx context.Context) (Counter, context.Context) {
ore.RegisterKeyedFunc[Counter](ore.Singleton, func(ctx context.Context) (Counter, context.Context) {
return &models.SimpleCounter{}, ctx
}, "name here", 1234)
}, "key-here")

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

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

ctx := context.Background()

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

// prints out: `TOTAL: 1`
Expand Down Expand Up @@ -321,9 +336,9 @@ Here how Ore can help you:
type Shutdowner interface {
Shutdown()
}
ore.RegisterEagerSingleton(&Logger{}) //*Logger implements Shutdowner
ore.RegisterEagerSingleton(&SomeRepository{}) //*SomeRepository implements Shutdowner
ore.RegisterEagerSingleton(&SomeService{}, "some_module") //*SomeService implements Shutdowner
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.
Expand Down Expand Up @@ -392,17 +407,17 @@ The `ore.GetResolvedScopedInstances[TInterface](context)` function returns a lis

### Multiple Containers (a.k.a Modules)

| DefaultContainer | Custom container |
|------------------|------------------|
| Get | GetFromContainer |
| GetList | GetListFromContainer |
| DefaultContainer | Custom container |
|-----------------------|------------------------------------|
| Get | GetFromContainer |
| GetList | GetListFromContainer |
| GetResolvedSingletons | GetResolvedSingletonsFromContainer |
| RegisterAlias | RegisterAliasToContainer |
| RegisterEagerSingleton | RegisterEagerSingletonToContainer |
| RegisterCreator | RegisterCreatorToContainer |
| RegisterFunc | RegisterFuncToContainer |
| RegisterPlaceHolder | RegisterPlaceHolderToContainer |
| ProvideScopedValue | ProvideScopedValueToContainer |
| 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:

Expand Down Expand Up @@ -439,13 +454,13 @@ A common scenario is that your "Service" depends on something which you couldn't
```go
//register SomeService which depends on "someConfig"
ore.RegisterFunc[*SomeService](ore.Scoped, func(ctx context.Context) (*SomeService, context.Context) {
someConfig, ctx := ore.Get[string](ctx, "someConfig")
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.RegisterPlaceHolder[string]("someConfig")
ore.RegisterKeyedPlaceholder[string]("someConfig")

//a new request arrive
ctx := context.Background()
Expand All @@ -455,14 +470,14 @@ 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.ProvideScopedValue(ctx, "Admin config", "someConfig")
ctx = ore.ProvideKeyedScopedValue(ctx, "Admin config", "someConfig")
} else if userRole == "supervisor" {
ctx = ore.ProvideScopedValue(ctx, "Supervisor config", "someConfig")
ctx = ore.ProvideKeyedScopedValue(ctx, "Supervisor config", "someConfig")
} else if userRole == "user" {
if (isAuthenticatedUser) {
ctx = ore.ProvideScopedValue(ctx, "Public user config", "someConfig")
ctx = ore.ProvideKeyedScopedValue(ctx, "Private user config", "someConfig")
} else {
ctx = ore.ProvideScopedValue(ctx, "Private user config", "someConfig")
ctx = ore.ProvideKeyedScopedValue(ctx, "Public user config", "someConfig")
}
}

Expand All @@ -473,7 +488,7 @@ fmt.Println(service.someConfig) //"Admin config"

([See full codes here](./examples/placeholderdemo/main.go))

- `ore.RegisterPlaceHolder[T](key...)` registers a future value with Scoped lifetime.
- `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.

Expand Down Expand Up @@ -530,15 +545,15 @@ goos: windows
goarch: amd64
pkg: github.com/firasdarwish/ore
cpu: 13th Gen Intel(R) Core(TM) i9-13900H
BenchmarkRegisterFunc-20 5706694 196.9 ns/op
BenchmarkRegisterCreator-20 6283534 184.5 ns/op
BenchmarkRegisterEagerSingleton-20 5146953 211.5 ns/op
BenchmarkInitialGet-20 3440072 352.1 ns/op
BenchmarkGet-20 9806043 121.8 ns/op
BenchmarkInitialGetList-20 1601787 747.9 ns/op
BenchmarkGetList-20 4237449 282.1 ns/op
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 11.427s
ok github.com/firasdarwish/ore 10.883s
```

Checkout also [examples/benchperf/README.md](examples/benchperf/README.md)
Expand Down
5 changes: 4 additions & 1 deletion benchmarks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func BenchmarkRegisterCreator(b *testing.B) {
}
}

func BenchmarkRegisterEagerSingleton(b *testing.B) {
func BenchmarkRegisterSingleton(b *testing.B) {
clearAll()

b.ResetTimer()
Expand Down Expand Up @@ -70,6 +70,8 @@ func BenchmarkGet(b *testing.B) {
RegisterCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{})
Seal()
Validate()
DefaultContainer.DisableValidation = true

ctx := context.Background()

b.ResetTimer()
Expand Down Expand Up @@ -111,6 +113,7 @@ func BenchmarkGetList(b *testing.B) {
RegisterCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{})
Seal()
Validate()
DefaultContainer.DisableValidation = true
ctx := context.Background()

b.ResetTimer()
Expand Down
18 changes: 9 additions & 9 deletions eager_singleton_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/assert"
)

func TestRegisterEagerSingleton(t *testing.T) {
func TestRegisterSingleton(t *testing.T) {
clearAll()

RegisterSingleton[interfaces.SomeCounter](&models.SimpleCounter{})
Expand All @@ -24,14 +24,14 @@ func TestRegisterEagerSingleton(t *testing.T) {
}
}

func TestRegisterEagerSingletonNilImplementation(t *testing.T) {
func TestRegisterSingletonNilImplementation(t *testing.T) {
clearAll()
assert.Panics(t, func() {
RegisterSingleton[interfaces.SomeCounter](nil)
})
}

func TestRegisterEagerSingletonMultipleImplementations(t *testing.T) {
func TestRegisterSingletonMultipleImplementations(t *testing.T) {
clearAll()

RegisterSingleton[interfaces.SomeCounter](&models.SimpleCounter{})
Expand All @@ -45,7 +45,7 @@ func TestRegisterEagerSingletonMultipleImplementations(t *testing.T) {
}
}

func TestRegisterEagerSingletonMultipleImplementationsKeyed(t *testing.T) {
func TestRegisterSingletonMultipleImplementationsKeyed(t *testing.T) {
clearAll()

RegisterKeyedSingleton[interfaces.SomeCounter](&models.SimpleCounter{}, "firas")
Expand All @@ -60,7 +60,7 @@ func TestRegisterEagerSingletonMultipleImplementationsKeyed(t *testing.T) {
}
}

func TestRegisterEagerSingletonSingletonState(t *testing.T) {
func TestRegisterSingletonSingletonState(t *testing.T) {
clearAll()

RegisterSingleton[interfaces.SomeCounter](&models.SimpleCounter{})
Expand All @@ -82,22 +82,22 @@ func TestRegisterEagerSingletonSingletonState(t *testing.T) {
}
}

func TestRegisterEagerSingletonNilKeyOnRegistering(t *testing.T) {
func TestRegisterSingletonNilKeyOnRegistering(t *testing.T) {
clearAll()
assert.Panics(t, func() {
RegisterKeyedSingleton[interfaces.SomeCounter](&models.SimpleCounter{}, nil)
})
}

func TestRegisterEagerSingletonNilKeyOnGetting(t *testing.T) {
func TestRegisterSingletonNilKeyOnGetting(t *testing.T) {
clearAll()
RegisterKeyedSingleton[interfaces.SomeCounter](&models.SimpleCounter{}, "firas")
assert.Panics(t, func() {
GetKeyed[interfaces.SomeCounter](context.Background(), nil)
})
}

func TestRegisterEagerSingletonGeneric(t *testing.T) {
func TestRegisterSingletonGeneric(t *testing.T) {
clearAll()

RegisterSingleton[interfaces.SomeCounterGeneric[uint]](&models.CounterGeneric[uint]{})
Expand All @@ -112,7 +112,7 @@ func TestRegisterEagerSingletonGeneric(t *testing.T) {
}
}

func TestRegisterEagerSingletonMultipleGenericImplementations(t *testing.T) {
func TestRegisterSingletonMultipleGenericImplementations(t *testing.T) {
clearAll()

RegisterSingleton[interfaces.SomeCounterGeneric[uint]](&models.CounterGeneric[uint]{})
Expand Down
12 changes: 6 additions & 6 deletions examples/benchperf/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,15 @@ DGa -.implement..-> G
On my machine, Ore always perform faster and use less memory than Samber/Do:

```text
goos: linux
goos: windows
goarch: amd64
pkg: examples/benchperf
cpu: 13th Gen Intel(R) Core(TM) i7-1365U
Benchmark_Ore-12 409480 2509 ns/op 2089 B/op 57 allocs/op
Benchmark_OreNoValidation-12 671000 1699 ns/op 1080 B/op 30 allocs/op
Benchmark_SamberDo-12 218361 4825 ns/op 2184 B/op 70 allocs/op
cpu: 13th Gen Intel(R) Core(TM) i9-13900H
Benchmark_Ore-20 448519 2427 ns/op 2233 B/op 57 allocs/op
Benchmark_OreNoValidation-20 814785 1477 ns/op 1080 B/op 30 allocs/op
Benchmark_SamberDo-20 246958 4891 ns/op 2184 B/op 70 allocs/op
PASS
ok examples/benchperf 4.222s
ok examples/benchperf 4.016s
```

As any benchmarks, please take these number "relatively" as a general idea:
Expand Down

0 comments on commit d82a8d1

Please sign in to comment.