diff --git a/README.md b/README.md index 1001100..ba4f8c8 100644 --- a/README.md +++ b/README.md @@ -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.
## Installation @@ -99,7 +113,7 @@ var c Counter c = &models.SimpleCounter{} // register -ore.RegisterEagerSingleton[Counter](c) +ore.RegisterSingleton[Counter](c) ctx := context.Background() @@ -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) @@ -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` @@ -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. @@ -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: @@ -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() @@ -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") } } @@ -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. @@ -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) diff --git a/benchmarks_test.go b/benchmarks_test.go index 0f63f1b..e28c401 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -27,7 +27,7 @@ func BenchmarkRegisterCreator(b *testing.B) { } } -func BenchmarkRegisterEagerSingleton(b *testing.B) { +func BenchmarkRegisterSingleton(b *testing.B) { clearAll() b.ResetTimer() @@ -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() @@ -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() diff --git a/eager_singleton_test.go b/eager_singleton_test.go index ef58731..76216b2 100644 --- a/eager_singleton_test.go +++ b/eager_singleton_test.go @@ -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{}) @@ -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{}) @@ -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") @@ -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{}) @@ -82,14 +82,14 @@ 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() { @@ -97,7 +97,7 @@ func TestRegisterEagerSingletonNilKeyOnGetting(t *testing.T) { }) } -func TestRegisterEagerSingletonGeneric(t *testing.T) { +func TestRegisterSingletonGeneric(t *testing.T) { clearAll() RegisterSingleton[interfaces.SomeCounterGeneric[uint]](&models.CounterGeneric[uint]{}) @@ -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]{}) diff --git a/examples/benchperf/README.md b/examples/benchperf/README.md index 0783bfe..c139466 100644 --- a/examples/benchperf/README.md +++ b/examples/benchperf/README.md @@ -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: