module

package module
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 23, 2024 License: MPL-2.0 Imports: 5 Imported by: 0

README

Go

Module, a type-based dependency injection framework for Go.

See usage examples.

Documentation

Overview

Package module provides a way to do dependency injection, with type-safe, without performance penalty. See [examples_test.go](./examples_test.go) for the basic usage.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNoPrivoder = fmt.Errorf("can't find module")

ErrNoPrivoder means that it can't find a module within a given context. Usually it misses adding that module to a repo.

Functions

This section is empty.

Types

type BuildFunc

type BuildFunc[T any] func(context.Context) (T, error)

BuildFunc is the constructor of an Instance.

type Module

type Module[T any] struct {
	// contains filtered or unexported fields
}

Module provides a module to inject and retreive an instance with its type.

Example
package main

import (
	"context"
	"fmt"

	"github.com/googollee/module"
)

type DB interface {
	Target() string
}

type db struct {
	target string
}

func (db *db) Target() string {
	return db.target
}

var ModuleDB = module.New[DB]()

type Cache struct {
	fallback  DB
	keyPrefix string
}

var (
	ModuleCache  = module.New[*Cache]()
	ProvideCache = ModuleCache.ProvideWithFunc(func(ctx context.Context) (*Cache, error) {
		db := ModuleDB.Value(ctx)
		return &Cache{
			fallback:  db,
			keyPrefix: "cache",
		}, nil
	})
)

func main() {
	repo := module.NewRepo()

	// No order required when adding providers
	repo.Add(ProvideCache)
	repo.Add(ModuleDB.ProvideValue(&db{target: "local.db"}))

	ctx := context.Background()

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

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

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

}
Output:

db target: local.db
cache fallback target: local.db
Example (CreateWithError)
package main

import (
	"context"
	"fmt"

	"github.com/googollee/module"
)

type DB interface {
	Target() string
}

type db struct {
	target string
}

func (db *db) Target() string {
	return db.target
}

var ModuleDB = module.New[DB]()

type Cache struct {
	fallback  DB
	keyPrefix string
}

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

func main() {
	repo := module.NewRepo()
	repo.Add(ModuleDB.ProvideValue(&db{target: "local.db"}))
	repo.Add(ModuleCache.ProvideWithFunc(func(ctx context.Context) (*Cache, error) {
		_ = ModuleDB.Value(ctx)
		return nil, fmt.Errorf("new cache error")
	}))

	ctx := context.Background()

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

}
Output:

inject error: creating with module *module_test.Cache: new cache error
Example (CreateWithPanic)
package main

import (
	"context"
	"fmt"

	"github.com/googollee/module"
)

type DB interface {
	Target() string
}

type db struct {
	target string
}

func (db *db) Target() string {
	return db.target
}

var ModuleDB = module.New[DB]()

type Cache struct {
	fallback  DB
	keyPrefix string
}

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

func main() {
	repo := module.NewRepo()
	repo.Add(ModuleDB.ProvideValue(&db{target: "localhost.db"}))
	repo.Add(ModuleCache.ProvideWithFunc(func(ctx context.Context) (*Cache, error) {
		_ = ModuleDB.Value(ctx)
		panic(fmt.Errorf("new cache error"))
	}))

	defer func() {
		err := recover()
		fmt.Println("panic:", err)
	}()

	ctx := context.Background()

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

}
Output:

panic: new cache error
Example (DuplicatingProviders)
package main

import (
	"fmt"
	"regexp"

	"github.com/googollee/module"
)

type DB interface {
	Target() string
}

type db struct {
	target string
}

func (db *db) Target() string {
	return db.target
}

var ModuleDB = module.New[DB]()

func main() {
	defer func() {
		p := recover().(string)
		// Remove the file line info for testing.
		fmt.Println("panic:", regexp.MustCompile(`at .*`).ReplaceAllString(p, "at <removed file and line>"))
	}()

	repo := module.NewRepo()
	repo.Add(ModuleDB.ProvideValue(&db{target: "real.db"}))
	repo.Add(ModuleDB.ProvideValue(&db{target: "fake.db"}))

}
Output:

panic: already have a provider with type "module_test.DB", added at <removed file and line>
Example (LoadOtherValue)
package main

import (
	"context"
	"fmt"

	"github.com/googollee/module"
)

type DB interface {
	Target() string
}

type db struct {
	target string
}

func (db *db) Target() string {
	return db.target
}

var ModuleDB = module.New[DB]()

type Cache struct {
	fallback  DB
	keyPrefix string
}

var (
	ModuleCache  = module.New[*Cache]()
	ProvideCache = ModuleCache.ProvideWithFunc(func(ctx context.Context) (*Cache, error) {
		db := ModuleDB.Value(ctx)
		return &Cache{
			fallback:  db,
			keyPrefix: "cache",
		}, nil
	})
)

func main() {
	type Key string
	targetKey := Key("target")

	repo := module.NewRepo()
	repo.Add(ModuleDB.ProvideWithFunc(func(ctx context.Context) (DB, error) {
		// Load the target value from the context.
		target := ctx.Value(targetKey).(string)

		return &db{
			target: target,
		}, nil
	}))
	repo.Add(ProvideCache)

	// Store the target value in the context.
	ctx := context.WithValue(context.Background(), targetKey, "target.db")

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

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

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

}
Output:

db target: target.db
cache fallback target: target.db
Example (NewPrefixInSpan)
package main

import (
	"context"
	"fmt"

	"github.com/googollee/module"
)

type DB interface {
	Target() string
}

type db struct {
	target string
}

func (db *db) Target() string {
	return db.target
}

var ModuleDB = module.New[DB]()

type Cache struct {
	fallback  DB
	keyPrefix string
}

var (
	ModuleCache  = module.New[*Cache]()
	ProvideCache = ModuleCache.ProvideWithFunc(func(ctx context.Context) (*Cache, error) {
		db := ModuleDB.Value(ctx)
		return &Cache{
			fallback:  db,
			keyPrefix: "cache",
		}, nil
	})
)

func main() {
	repo := module.NewRepo()
	repo.Add(ModuleDB.ProvideValue(&db{target: "local.db"}))
	repo.Add(ProvideCache)

	ctx := context.Background()

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

	db := ModuleDB.Value(ctx)
	cache := ModuleCache.Value(ctx)
	fmt.Println("before span, db target:", db.Target())
	fmt.Println("before span, cache prefix:", cache.keyPrefix)

	{
		// a new context in the span
		ctx := ModuleCache.With(ctx, &Cache{
			fallback:  db,
			keyPrefix: "span",
		})

		db := ModuleDB.Value(ctx)
		cache := ModuleCache.Value(ctx)
		fmt.Println("in span, db target:", db.Target())
		fmt.Println("in span, cache prefix:", cache.keyPrefix)
	}

	db = ModuleDB.Value(ctx)
	cache = ModuleCache.Value(ctx)
	fmt.Println("after span, db target:", db.Target())
	fmt.Println("after span, cache fallback target:", cache.keyPrefix)

}
Output:

before span, db target: local.db
before span, cache prefix: cache
in span, db target: local.db
in span, cache prefix: span
after span, db target: local.db
after span, cache fallback target: cache
Example (NotExistingProvider)
package main

import (
	"context"
	"fmt"

	"github.com/googollee/module"
)

type DB interface {
	Target() string
}

var ModuleDB = module.New[DB]()

type Cache struct {
	fallback  DB
	keyPrefix string
}

var (
	ModuleCache  = module.New[*Cache]()
	ProvideCache = ModuleCache.ProvideWithFunc(func(ctx context.Context) (*Cache, error) {
		db := ModuleDB.Value(ctx)
		return &Cache{
			fallback:  db,
			keyPrefix: "cache",
		}, nil
	})
)

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

	repo := module.NewRepo()
	repo.Add(ProvideCache)
	// repo.Add(ModuleDB.ProvideValue())

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

}
Output:

inject error: creating with module module_test.DB: can't find module

func New

func New[T any]() Module[T]

New creates a new module with type `T` and the constructor `builder`.

func (Module[T]) ProvideValue

func (m Module[T]) ProvideValue(value T) Provider

ProvideValue returns a provider which always provides given `value` as instances.

func (Module[T]) ProvideWithFunc

func (m Module[T]) ProvideWithFunc(ctor BuildFunc[T]) Provider

ProvideWithFunc returns a provider which provides instances creating from `ctor` function.

func (Module[T]) Value

func (m Module[T]) Value(ctx context.Context) T

Value returns an instance of T which is injected to the context.

func (Module[T]) With

func (m Module[T]) With(ctx context.Context, t T) context.Context

type Provider

type Provider interface {
	// contains filtered or unexported methods
}

Provider is the interface to provide an Instance.

type Repo

type Repo struct {
	// contains filtered or unexported fields
}

Repo is a repository of modules, and to inject instances creating by modules into a context.

func NewRepo

func NewRepo() *Repo

NewRepo creates a Repo instance.

func (*Repo) Add

func (r *Repo) Add(provider Provider)

Add adds a provider to the repo.

func (*Repo) InjectTo

func (r *Repo) InjectTo(ctx context.Context) (context.Context, error)

InjectTo injects instances created by modules into a context `ctx`. It returns a new context with all injections. If any module creates an instance with an error, `InjectTo` returns that error with the module name. Injecting instances only create once if necessary. Calling `InjectTo` mutlple times share instances between returning contexts. InjectTo ignores all new providers adding to the Repo after the first run. So adding all providers before calling `InjectTo`.

Directories

Path Synopsis
Package log is a module injecting a `*slog.Logger` instance.
Package log is a module injecting a `*slog.Logger` instance.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL