Skip to content

Commit

Permalink
Abstract Provider from Module.
Browse files Browse the repository at this point in the history
  • Loading branch information
googollee committed Apr 10, 2024
1 parent 777c49e commit 20f686b
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 90 deletions.
17 changes: 10 additions & 7 deletions module/corners_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ func TestProviderReturnNilInterfaceWithoutError(t *testing.T) {
newNilInterface := func(context.Context) (Interface, error) {
return nil, nil
}
moduleInterface := New(newNilInterface)
moduleInterface := New[Interface]()
provideNil := moduleInterface.ProvideWithFunc(newNilInterface)

repo := NewRepo()
repo.AddModule(moduleInterface)
repo.AddModule(provideNil)

ctx, err := repo.InjectTo(context.Background())
if err != nil {
Expand All @@ -31,10 +32,11 @@ func TestProviderReturnNilPointerWithoutError(t *testing.T) {
newNilInstance := func(context.Context) (*Instance, error) {
return nil, nil
}
moduleInstance := New(newNilInstance)
moduleInstance := New[*Instance]()
provideNil := moduleInstance.ProvideWithFunc(newNilInstance)

repo := NewRepo()
repo.AddModule(moduleInstance)
repo.AddModule(provideNil)

ctx, err := repo.InjectTo(context.Background())
if err != nil {
Expand All @@ -52,10 +54,11 @@ func TestModuleKeyIsNotString(t *testing.T) {
newNilInstance := func(context.Context) (*Instance, error) {
return &Instance{}, nil
}
moduleInstance := New(newNilInstance)
moduleInstance := New[*Instance]()
provideNil := moduleInstance.ProvideWithFunc(newNilInstance)

repo := NewRepo()
repo.AddModule(moduleInstance)
repo.AddModule(provideNil)

ctx, err := repo.InjectTo(context.Background())
if err != nil {
Expand All @@ -66,7 +69,7 @@ func TestModuleKeyIsNotString(t *testing.T) {
t.Errorf("moduleInstance.Value(ctx) = nil, want: not nil")
}

key := moduleInstance.key()
key := moduleInstance.moduleKey
if got := ctx.Value(key); got == nil {
t.Errorf("moduleInstance.Value(ctx) = nil, want: not nil")
}
Expand Down
90 changes: 39 additions & 51 deletions module/examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ func (db *db) Target() string {
return db.target
}

var ModuleDB = module.New(NewDB)
var (
ModuleDB = module.New[DB]()
ProvideDB = ModuleDB.ProvideWithFunc(NewDB)
)

type Cache struct {
fallback DB
Expand All @@ -39,14 +42,17 @@ func NewCache(ctx context.Context) (*Cache, error) {
}, nil
}

var ModuleCache = module.New(NewCache)
var (
ModuleCache = module.New[*Cache]()
ProvideCache = ModuleCache.ProvideWithFunc(NewCache)
)

func ExampleModule() {
ctx := context.Background()

repo := module.NewRepo()
repo.AddModule(ModuleCache)
repo.AddModule(ModuleDB)
repo.AddModule(ProvideCache)
repo.AddModule(ProvideDB)

ctx, err := repo.InjectTo(ctx)
if err != nil {
Expand All @@ -69,37 +75,29 @@ func ExampleModule_withOtherValue() {
targetKey := "target"
ctx := context.WithValue(context.Background(), targetKey, "target.db")

newDB := func(ctx context.Context) (DB, error) {
repo := module.NewRepo()
repo.AddModule(ModuleDB.ProvideWithFunc(func(ctx context.Context) (DB, error) {
target := ctx.Value(targetKey).(string)
return &db{
target: target,
}, nil
}
moduleDB := module.New(newDB)

newCache := func(ctx context.Context) (*Cache, error) {
db := moduleDB.Value(ctx)
}))
repo.AddModule(ModuleCache.ProvideWithFunc(func(ctx context.Context) (*Cache, error) {
db := ModuleDB.Value(ctx)
return &Cache{
fallback: db,
}, nil
}
moduleCache := module.New(newCache)

repo := module.NewRepo()
repo.AddModule(moduleCache)
repo.AddModule(moduleDB)
}))

ctx, err := repo.InjectTo(ctx)
if err != nil {
fmt.Println("inject error:", err)
return
}

db := moduleDB.Value(ctx)
cache := moduleCache.Value(ctx)
db := ModuleDB.Value(ctx)
cache := ModuleCache.Value(ctx)

_ = db
_ = cache
fmt.Println("db target:", db.Target())
fmt.Println("cache fallback target:", cache.fallback.Target())

Expand All @@ -111,22 +109,16 @@ func ExampleModule_withOtherValue() {
func ExampleModule_createWithError() {
ctx := context.Background()

newDB := func(ctx context.Context) (DB, error) {
repo := module.NewRepo()
repo.AddModule(ModuleDB.ProvideWithFunc(func(ctx context.Context) (DB, error) {
return &db{
target: "localhost.db",
}, nil
}
moduleDB := module.New(newDB)

newCache := func(ctx context.Context) (*Cache, error) {
_ = moduleDB.Value(ctx)
}))
repo.AddModule(ModuleCache.ProvideWithFunc(func(ctx context.Context) (*Cache, error) {
_ = ModuleDB.Value(ctx)
return nil, fmt.Errorf("new cache error")
}
moduleCache := module.New(newCache)

repo := module.NewRepo()
repo.AddModule(moduleCache)
repo.AddModule(moduleDB)
}))

_, err := repo.InjectTo(ctx)
if err != nil {
Expand All @@ -141,22 +133,16 @@ func ExampleModule_createWithError() {
func ExampleModule_createWithPanic() {
ctx := context.Background()

newDB := func(ctx context.Context) (DB, error) {
repo := module.NewRepo()
repo.AddModule(ModuleDB.ProvideWithFunc(func(ctx context.Context) (DB, error) {
return &db{
target: "localhost.db",
}, nil
}
moduleDB := module.New(newDB)

newCache := func(ctx context.Context) (*Cache, error) {
_ = moduleDB.Value(ctx)
}))
repo.AddModule(ModuleCache.ProvideWithFunc(func(ctx context.Context) (*Cache, error) {
_ = ModuleDB.Value(ctx)
panic(fmt.Errorf("new cache error"))
}
moduleCache := module.New(newCache)

repo := module.NewRepo()
repo.AddModule(moduleCache)
repo.AddModule(moduleDB)
}))

defer func() {
err := recover()
Expand All @@ -177,8 +163,8 @@ func ExampleModule_notExistingProvider() {
ctx := context.Background()

repo := module.NewRepo()
repo.AddModule(ModuleCache)
// repo.AddModule(moduleDB)
repo.AddModule(ProvideCache)
// repo.AddModule(ProvideDB)

_, err := repo.InjectTo(ctx)
if err != nil {
Expand All @@ -201,8 +187,6 @@ func NewRealFileSystem(context.Context) (FileSystem, error) {
return &realFileSystem{}, nil
}

var RealFileSystem = module.New(NewRealFileSystem)

func (f *realFileSystem) Read() {}
func (f *realFileSystem) Write() {}

Expand All @@ -212,7 +196,11 @@ func NewMockFileSystem(context.Context) (FileSystem, error) {
return &mockFileSystem{}, nil
}

var MockFileSystem = module.New(NewMockFileSystem)
var (
ModuleFileSystem = module.New[FileSystem]()
ProvideRealFileSystem = ModuleFileSystem.ProvideWithFunc(NewRealFileSystem)
ProvideMockFileSystem = ModuleFileSystem.ProvideWithFunc(NewMockFileSystem)
)

func (f *mockFileSystem) Read() {}
func (f *mockFileSystem) Write() {}
Expand All @@ -224,8 +212,8 @@ func ExampleModule_duplicatingProviders() {
}()

repo := module.NewRepo()
repo.AddModule(RealFileSystem)
repo.AddModule(MockFileSystem)
repo.AddModule(ProvideRealFileSystem)
repo.AddModule(ProvideMockFileSystem)

// Output:
// panic: already have a provider with type "module_test.FileSystem", added at <removed file and line>
Expand Down
22 changes: 2 additions & 20 deletions module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,16 @@ type BuildFunc[T any] func(context.Context) (T, error)

type moduleKey string

// Provider is the interface to provide an Instance.
type Provider interface {
key() moduleKey
value(ctx context.Context) (any, error)
}

// Module provides a module to inject an instance with its type.
// As Module implements Provider interface, a Module instance could be added into a Repo instance.
// Module provides a module to inject and retreive an instance with its type.
type Module[T any] struct {
Provider
moduleKey moduleKey
builder BuildFunc[T]
}

// New creates a new module with type `T` and the constructor `builder`.
func New[T any](builder BuildFunc[T]) Module[T] {
func New[T any]() Module[T] {
var t T
return Module[T]{
moduleKey: moduleKey(reflect.TypeOf(&t).Elem().String()),
builder: builder,
}
}

Expand All @@ -46,11 +36,3 @@ func (m Module[T]) Value(ctx context.Context) T {

return v.(T)
}

func (m Module[T]) key() moduleKey {
return m.moduleKey
}

func (m Module[T]) value(ctx context.Context) (any, error) {
return m.builder(ctx)
}
23 changes: 11 additions & 12 deletions module/perf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ func NewDB(ctx context.Context) (*DB, error) {
}, nil
}

var moduleDB = New(NewDB)

func (*DB) CheckHealth(context.Context) error { return nil }

type Cache struct {
fallback *DB
}
Expand All @@ -30,14 +26,17 @@ func NewCache(ctx context.Context) (*Cache, error) {
}, nil
}

var moduleCache = New(NewCache)

func (*Cache) CheckHealth(context.Context) error { return nil }
var (
moduleDB = New[*DB]()
moduleCache = New[*Cache]()
provideDB = moduleDB.ProvideWithFunc(NewDB)
provideCache = moduleCache.ProvideWithFunc(NewCache)
)

func BenchmarkThroughModuleValue(b *testing.B) {
repo := NewRepo()
repo.AddModule(moduleDB)
repo.AddModule(moduleCache)
repo.AddModule(provideDB)
repo.AddModule(provideCache)

ctx, err := repo.InjectTo(context.Background())
if err != nil {
Expand All @@ -57,16 +56,16 @@ func BenchmarkSimpleContextValue(b *testing.B) {
if err != nil {
b.Fatal("create db error:", err)
}
ctx = context.WithValue(ctx, moduleDB.key(), db)
ctx = context.WithValue(ctx, moduleDB.moduleKey, db)

cache, err := NewCache(ctx)
if err != nil {
b.Fatal("create cache error:", err)
}
ctx = context.WithValue(ctx, moduleCache.key(), cache)
ctx = context.WithValue(ctx, moduleCache.moduleKey, cache)

b.ResetTimer()
for i := 0; i < b.N; i++ {
var _ *Cache = ctx.Value(moduleCache.key()).(*Cache)
var _ *Cache = ctx.Value(moduleCache.moduleKey).(*Cache)
}
}
30 changes: 30 additions & 0 deletions module/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package module

import "context"

// Provider is the interface to provide an Instance.
type Provider interface {
key() moduleKey
value(ctx context.Context) (any, error)
}

type funcProvider[T any] struct {
moduleKey moduleKey
ctor BuildFunc[T]
}

// ProvideWithFunc returns a provider which provides instances creating from `ctor` function.
func (m *Module[T]) ProvideWithFunc(ctor BuildFunc[T]) Provider {
return &funcProvider[T]{
moduleKey: m.moduleKey,
ctor: ctor,
}
}

func (p funcProvider[T]) key() moduleKey {
return p.moduleKey
}

func (p funcProvider[T]) value(ctx context.Context) (any, error) {
return p.ctor(ctx)
}

0 comments on commit 20f686b

Please sign in to comment.