Skip to content

Commit

Permalink
R/0.4.1 (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
firasdarwish authored Nov 11, 2024
2 parents ce06b0c + 215eb30 commit 5185de5
Show file tree
Hide file tree
Showing 8 changed files with 302 additions and 37 deletions.
32 changes: 17 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,13 +326,13 @@ Alias is also scoped by key. When you "Get" an alias with keys for eg: `ore.Get[

### Registration Validation

Once you're done with registering all the services, it is recommended to call `ore.Validate()`.
Once you're done with registering all the services, it is recommended to call `ore.Build()` AND `ore.Validate()`.

`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).
- 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).

<br />

Expand All @@ -347,7 +347,10 @@ Option 1 (run `ore.Validate` on test) is usually a better choice.

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

(3) Keep the object creation function (a.k.a resolvers) simple. Their only responsibility should be **object creation**.
(3) A combination of `ore.Buile()` and then `ore.Validate()` ensures no more new resolvers will be registered AND all registered resolvers are validated, this will automatically
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
Expand Down Expand Up @@ -492,16 +495,15 @@ 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
BenchmarkRegisterLazyFunc-20 5706694 196.9 ns/op
BenchmarkRegisterLazyCreator-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
PASS
ok github.com/firasdarwish/ore 11.427s
```

Checkout also [examples/benchperf/README.md](examples/benchperf/README.md)
Expand Down
4 changes: 2 additions & 2 deletions alias_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ func TestAliasResolverConflict(t *testing.T) {
ctx := context.Background()

//The last registered IPerson is "Mary Transient", it would normally takes precedence.
//However we registered a direct resolver for IPerson which is "Peter Singleton".
//However, we registered a direct resolver for IPerson which is "Peter Singleton".
//So Ore won't treat IPerson as an alias and will resolve IPerson directly as "Peter Singleton"
person, ctx := Get[m.IPerson](ctx)
assert.Equal(t, person.(*m.Trader).Name, "Peter Singleton")

//GetlList will return all possible IPerson whatever alias or from direct resolver.
//GetList will return all possible IPerson whatever alias or from direct resolver.
personList, _ := GetList[m.IPerson](ctx)
assert.Equal(t, len(personList), 2)
}
Expand Down
49 changes: 47 additions & 2 deletions benchmarks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func BenchmarkRegisterEagerSingleton(b *testing.B) {
}
}

func BenchmarkGet(b *testing.B) {
func BenchmarkInitialGet(b *testing.B) {
clearAll()

RegisterLazyFunc[someCounter](Scoped, func(ctx context.Context) (someCounter, context.Context) {
Expand All @@ -45,6 +45,9 @@ func BenchmarkGet(b *testing.B) {

RegisterLazyCreator[someCounter](Scoped, &simpleCounter{})

Build()
Validate()

ctx := context.Background()

b.ResetTimer()
Expand All @@ -53,7 +56,7 @@ func BenchmarkGet(b *testing.B) {
}
}

func BenchmarkGetList(b *testing.B) {
func BenchmarkGet(b *testing.B) {
clearAll()

RegisterLazyFunc[someCounter](Scoped, func(ctx context.Context) (someCounter, context.Context) {
Expand All @@ -63,6 +66,28 @@ func BenchmarkGetList(b *testing.B) {
RegisterEagerSingleton[someCounter](&simpleCounter{})

RegisterLazyCreator[someCounter](Scoped, &simpleCounter{})
Build()
Validate()
ctx := context.Background()

b.ResetTimer()
for i := 0; i < b.N; i++ {
_, ctx = Get[someCounter](ctx)
}
}

func BenchmarkInitialGetList(b *testing.B) {
clearAll()

RegisterLazyFunc[someCounter](Scoped, func(ctx context.Context) (someCounter, context.Context) {
return &simpleCounter{}, ctx
})

RegisterEagerSingleton[someCounter](&simpleCounter{})

RegisterLazyCreator[someCounter](Scoped, &simpleCounter{})
Build()
Validate()

ctx := context.Background()

Expand All @@ -71,3 +96,23 @@ func BenchmarkGetList(b *testing.B) {
GetList[someCounter](ctx)
}
}

func BenchmarkGetList(b *testing.B) {
clearAll()

RegisterLazyFunc[someCounter](Scoped, func(ctx context.Context) (someCounter, context.Context) {
return &simpleCounter{}, ctx
})

RegisterEagerSingleton[someCounter](&simpleCounter{})

RegisterLazyCreator[someCounter](Scoped, &simpleCounter{})
Build()
Validate()
ctx := context.Background()

b.ResetTimer()
for i := 0; i < b.N; i++ {
_, ctx = GetList[someCounter](ctx)
}
}
66 changes: 58 additions & 8 deletions key.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,77 @@ package ore

import (
"fmt"
"strconv"
"strings"
)

type KeyStringer any

func oreKey(key []KeyStringer) string {
l := len(key)
type stringer interface {
String() string
}

if key == nil || l == 0 {
func oreKey(key []KeyStringer) string {
if key == nil {
return ""
}

l := len(key)

if l == 1 {
return fmt.Sprintf("%v", key[0])
return stringifyOreKey(key[0])
}

var sb strings.Builder

for _, s := range key {
sb.WriteString(stringifyOreKey(s))
}

var keys []string
return sb.String()
}

for _, stringer := range key {
keys = append(keys, fmt.Sprintf("%v", stringer))
func stringifyOreKey(key KeyStringer) string {
switch key.(type) {
case nil:
return ""
case string:
return key.(string)
case bool:
return strconv.FormatBool(key.(bool))
case int:
return strconv.Itoa(key.(int))
case int8:
return strconv.FormatInt(int64(key.(int8)), 36)
case int16:
return strconv.FormatInt(int64(key.(int16)), 36)
case int32:
return strconv.FormatInt(int64(key.(int32)), 36)
case int64:
return strconv.FormatInt(key.(int64), 36)

case uint:
return strconv.FormatUint(uint64(key.(uint)), 36)
case uint8:
return strconv.FormatUint(uint64(key.(uint8)), 36)
case uint16:
return strconv.FormatUint(uint64(key.(uint16)), 36)
case uint32:
return strconv.FormatUint(uint64(key.(uint32)), 36)
case uint64:
return strconv.FormatUint(key.(uint64), 36)
case float32:
return strconv.FormatFloat(float64(key.(float32)), 'x', -1, 32)
case float64:
return strconv.FormatFloat(key.(float64), 'x', -1, 64)
case stringer:
return key.(stringer).String()

default:
return stringifyOreKeyUnknown(key)
}
}

return strings.Join(keys, ":/#+")
func stringifyOreKeyUnknown(key KeyStringer) string {
return fmt.Sprintf("%v", key)
}
147 changes: 147 additions & 0 deletions key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package ore

import (
"testing"
)

func TestOreKeyNil(t *testing.T) {
k := oreKey(nil)

if got := k; got != "" {
t.Errorf("got `%v`, expected `%v`", got, "")
}
}

func TestOreKeyEmpty(t *testing.T) {
k := oreKey([]KeyStringer{})

if got := k; got != "" {
t.Errorf("got `%v`, expected `%v`", got, "")
}
}

func TestOreKey1String(t *testing.T) {
k := oreKey([]KeyStringer{"ore"})
expect := "ore"

if got := k; got != expect {
t.Errorf("got `%v`, expected `%v`", got, expect)
}
}

func TestOreKey2String(t *testing.T) {
k := oreKey([]KeyStringer{"ore", "package"})
expect := "orepackage"

if got := k; got != expect {
t.Errorf("got `%v`, expected `%v`", got, expect)
}
}

func TestOreKey1Int(t *testing.T) {
k := oreKey([]KeyStringer{10})
expect := "10"

if got := k; got != expect {
t.Errorf("got `%v`, expected `%v`", got, expect)
}
}

func TestOreKey2Int(t *testing.T) {
k := oreKey([]KeyStringer{10, 30})
expect := "1030"

if got := k; got != expect {
t.Errorf("got `%v`, expected `%v`", got, expect)
}
}

func TestOreKeyStringInt(t *testing.T) {
k := oreKey([]KeyStringer{"ore", 97})
expect := "ore97"

if got := k; got != expect {
t.Errorf("got `%v`, expected `%v`", got, expect)
}
}

func TestOreKey2StringInt(t *testing.T) {
k := oreKey([]KeyStringer{"ore", 97, "di", 5})
expect := "ore97di5"

if got := k; got != expect {
t.Errorf("got `%v`, expected `%v`", got, expect)
}
}

func TestOreKeyUint(t *testing.T) {
var n uint
n = 5
k := oreKey([]KeyStringer{n})
expect := "5"

if got := k; got != expect {
t.Errorf("got `%v`, expected `%v`", got, expect)
}
}

func TestOreKeyFloat32(t *testing.T) {
var n float32
n = 5.751
k := oreKey([]KeyStringer{n})
expect := "0x1.701062p+02"

if got := k; got != expect {
t.Errorf("got `%v`, expected `%v`", got, expect)
}
}

func TestOreKeyFloat64(t *testing.T) {
var n float64
n = 5.7519
k := oreKey([]KeyStringer{n})
expect := "0x1.701f212d77319p+02"

if got := k; got != expect {
t.Errorf("got `%v`, expected `%v`", got, expect)
}
}

func TestOreKeyStringer(t *testing.T) {
n := &c{
Counter: 16,
}

k := oreKey([]KeyStringer{n})
expect := "Counter is: 16"

if got := k; got != expect {
t.Errorf("got `%v`, expected `%v`", got, expect)
}
}

func TestOreKeyStruct(t *testing.T) {
n := &simpleCounter{
counter: 17,
}

k := oreKey([]KeyStringer{n})
expect := "&{17}"

if got := k; got != expect {
t.Errorf("got `%v`, expected `%v`", got, expect)
}
}

func TestOreKeyVarious(t *testing.T) {
k := oreKey([]KeyStringer{"firas", 16, "ore", 3.14, 1 / 6, -9, -1494546.452, &simpleCounter{
counter: 17,
}, &c{
Counter: 18,
}})
expect := "firas16ore0x1.91eb851eb851fp+010-9-0x1.6ce1273b645a2p+20&{17}Counter is: 18"

if got := k; got != expect {
t.Errorf("got `%v`, expected `%v`", got, expect)
}
}
Loading

0 comments on commit 5185de5

Please sign in to comment.