README
¶
mud
mud is a package for lazily starting up a large set of services and chores.
The mud
package name comes from a common architectural pattern called "Big Ball of Mud".
You can think about mud as a very huge map of service instances: map[type]component
.
Where component includes the singleton instance and functions to initialize the singleton and run/close them.
Components can also depend on each other, and there are helper function to filter the required components (and/or initialize/start them).
Compared to other similar libraries, like https://github.com/uber-go/fx or https://github.com/uber-go/dig, mud is just a very flexible framework, it wouldn't like to restrict the usage. Therefore advanced workflows also can be implemented with filtering different graphs and using them.
Users of this library has more power (and more responsibility).
Getting started
You can create the instance registry with:
mud := mud.NewBall()
Register a new component:
Provide[your.Service](ball, func() your.Service {
return your.NewService()
})
Now, your component is registered, but not yet initialized. You should select some of the services to Init / run them:
err := mud.ForEach(ball, mud.Initialize(context.TODO()))
if err != nil {
panic(err)
}
Now your component is initialized:
fmt.Println(mud.Find(ball, mud.All)[0].Instance())
This one selected the first component (we registered only one), but you can also use different selectors. This one selects the components by type.
fmt.Println(mud.Find(ball, mud.Select[your.Service](ball))[0].Instance())
Or, of you are sure, it's there:
fmt.Println(mud.MustLookup[your.Service](ball))
Dependencies and dependency injection
Dependencies are automatically injected. Let's say you have two structs:
type Service struct {
}
func NewService() Service {
return Service{}
}
type Endpoint struct {
Service Service
}
func NewEndpoint(service Service) Endpoint {
return Endpoint{
Service: service,
}
}
Now you can register both:
mud.Provide[your.Service](ball, your.NewService)
mud.Provide[your.Endpoint](ball, your.NewEndpoint)
When you initialize the Endpoint, Service will be injected (if the instance is available!!!):
err := mud.MustDo[your.Service](ball, mud.Initialize(context.TODO()))
if err != nil {
panic(err)
}
err = mud.MustDo[your.Endpoint](ball, mud.Initialize(context.TODO()))
if err != nil {
panic(err)
}
But instead of initializing manually, you can also just ask what you need, and initialize everything in the right order
err := mud.ForEachDependency(ball, mud.Select[your.Endpoint](ball), mud.Initialize(context.TODO()), mud.All)
Views
Views are useful when you already have sg. registered, but you would like to make it fully or partially available under different type:
mud.Provide[satellite.DB](ball, OpenSatelliteDB)
mud.View[satellite.DB, gracefulexit.DB](ball, satellite.DB.GracefulExit)
This registers a satellite.DB
(first line) and a gracefulexit.DB
(second line). And if gracefulexit.DB
is needed
for injection, it will call the function to get it.
Documentation
¶
Index ¶
- func AddTagOf[TAG any](c *Component, tag TAG)
- func All(_ *Component) bool
- func CloseAll(ball *Ball, timeout time.Duration) error
- func DependsOn[BASE any, DEPENDENCY any](ball *Ball)
- func Dereference[A any](a *A) A
- func DisableImplementation[BASE any](ball *Ball)
- func DisableImplementationOf(c *Component)
- func Dot(w io.Writer, components []*Component) (err error)
- func DotAll(w io.Writer, ball *Ball) (err error)
- func Execute[A any](ctx context.Context, ball *Ball, factory interface{}, options ...any) (A, error)
- func Execute0(ctx context.Context, ball *Ball, factory interface{}) error
- func Factory[A any](ball *Ball, factory interface{})
- func ForEach(ball *Ball, callback func(component *Component) error, ...) error
- func ForEachDependency(ball *Ball, target ComponentSelector, ...) error
- func ForEachDependencyReverse(ball *Ball, target ComponentSelector, ...) error
- func GenerateComponentsGraph(fileprefix string, components []*Component) error
- func GetTag[A any, Tag any](ball *Ball) (Tag, bool)
- func GetTagOf[Tag any](c *Component) (Tag, bool)
- func Implementation[L ~[]T, Instance any, T any](ball *Ball)
- func Initialize(ctx context.Context) func(c *Component) error
- func MustGenerateGraph(ball *Ball, fileprefix string, selector ComponentSelector)
- func MustLookup[T any](ball *Ball) T
- func Provide[A any](ball *Ball, factory interface{}, options ...any)
- func RegisterImplementation[L ~[]T, T any](ball *Ball)
- func RegisterInterfaceImplementation[BASE any, DEP any](ball *Ball)
- func RegisterManual[T any](ball *Ball, factory func(ctx context.Context) (T, error))
- func RemoveTag[A any, Tag any](ball *Ball)
- func RemoveTagOf[TAG any](c *Component)
- func ReplaceDependency[BASE any, DEP any](ball *Ball)
- func ReplaceDependencyOf(from *Component, to *Component)
- func RunWithDependencies(ctx context.Context, ball *Ball, selector ComponentSelector) error
- func Supply[T any](ball *Ball, t T)
- func Tag[A any, Tag any](ball *Ball, tag Tag)
- func Tagged[Tag any]() func(c *Component) bool
- func View[From any, To any](ball *Ball, convert func(From) To)
- type Ball
- type Component
- func (c *Component) AddRequirement(in reflect.Type)
- func (c *Component) Close(ctx context.Context) error
- func (c *Component) GetTarget() reflect.Type
- func (c *Component) ID() string
- func (c *Component) Init(ctx context.Context) error
- func (c *Component) Instance() any
- func (c *Component) Name() string
- func (c *Component) Run(ctx context.Context, eg *errgroup.Group) error
- func (c *Component) String() string
- type ComponentSelector
- type CustomDotNode
- type Injector
- type Interface
- type Nullable
- type Optional
- type Stage
- type StageName
- type Wrapper
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AddTagOf ¶
func AddTagOf[TAG any](c *Component, tag TAG)
AddTagOf attaches a tag to the component registration.
func CloseAll ¶
func CloseAll(ball *Ball, timeout time.Duration) error
CloseAll calls the close callback stage on all initialized components.
func DependsOn ¶
func DependsOn[BASE any, DEPENDENCY any](ball *Ball)
DependsOn creates a dependency relation between two components. With the help of the dependency graph, they can be executed in the right order.
func Dereference ¶
func Dereference[A any](a *A) A
Dereference is a simple transformation to make real value from a pointer. Useful with View. for example: `View[*DB, DB](ball, Dereference[DB])`.
func DisableImplementation ¶
func DisableImplementation[BASE any](ball *Ball)
DisableImplementation removes the implementation from the list of dependencies.
func DisableImplementationOf ¶
func DisableImplementationOf(c *Component)
DisableImplementationOf is like DisableImplementation, but using components instead of generics.
func Dot ¶
func Dot(w io.Writer, components []*Component) (err error)
Dot generates graph report of the modules in dot format, but only the selected components are included.
func DotAll ¶
func DotAll(w io.Writer, ball *Ball) (err error)
DotAll generates graph report of the modules in dot format.
func Execute ¶
func Execute[A any](ctx context.Context, ball *Ball, factory interface{}, options ...any) (A, error)
Execute executes a function with injecting all the required dependencies with type based Dependency Injection.
func Execute0 ¶
func Execute0(ctx context.Context, ball *Ball, factory interface{}) error
Execute0 executes a function with injection all required parameters. Same as Execute but without return type.
func Factory ¶
func Factory[A any](ball *Ball, factory interface{})
Factory is like Provide, but instead of storing an instance in the component, it stores a factory function. factory function is called each time when the instance is required. Useful for logger, which requires further adjustment when it's injected.
func ForEach ¶
func ForEach(ball *Ball, callback func(component *Component) error, selectors ...ComponentSelector) error
ForEach executes a callback action on all the selected components.
func ForEachDependency ¶
func ForEachDependency(ball *Ball, target ComponentSelector, callback func(component *Component) error, selectors ...ComponentSelector) error
ForEachDependency executes a callback action on all the components, matching the target selector and dependencies, but only if selectors parameter is matching them.
func ForEachDependencyReverse ¶
func ForEachDependencyReverse(ball *Ball, target ComponentSelector, callback func(component *Component) error, selectors ...ComponentSelector) error
ForEachDependencyReverse executes a callback action on all the components in reverse order, matching the target selector and dependencies, but only if selectors parameter is matching them.
func GenerateComponentsGraph ¶
func GenerateComponentsGraph(fileprefix string, components []*Component) error
GenerateComponentsGraph generates dot and svg file including the selected components.
func GetTag ¶
func GetTag[A any, Tag any](ball *Ball) (Tag, bool)
GetTag returns with attached tag (if attached).
func GetTagOf ¶
func GetTagOf[Tag any](c *Component) (Tag, bool)
GetTagOf returns with attached tag (if attached).
func Implementation ¶
func Implementation[L ~[]T, Instance any, T any](ball *Ball)
Implementation registers a new []T component, which will be filled with any registered instances. Instances will be marked with "Optional{}" tag, and will be injected only, if they are initialized. It's the responsibility of the Init code to exclude / include them during initialization.
func Initialize ¶
func Initialize(ctx context.Context) func(c *Component) error
Initialize components as ForEach callback.
func MustGenerateGraph ¶
func MustGenerateGraph(ball *Ball, fileprefix string, selector ComponentSelector)
MustGenerateGraph generates dot and svg files from components selected by the selector.
func MustLookup ¶
func MustLookup[T any](ball *Ball) T
MustLookup returns with the registered component instance (or panic).
func Provide ¶
func Provide[A any](ball *Ball, factory interface{}, options ...any)
Provide registers a new instance to the dependency pool. Run/Close methods are auto-detected (stage is created if they exist).
func RegisterImplementation ¶
func RegisterImplementation[L ~[]T, T any](ball *Ball)
RegisterImplementation registers the implementation interface, without adding concrete implementation.
func RegisterInterfaceImplementation ¶
func RegisterInterfaceImplementation[BASE any, DEP any](ball *Ball)
RegisterInterfaceImplementation registers an interface with an implementation. Later the implementation can be replaced. Only one (or zero) implementation can be registered/used at the same time.
func RegisterManual ¶
func RegisterManual[T any](
ball *Ball,
factory func(ctx context.Context) (T, error),
)
RegisterManual registers a component manually. Most of the time you need either Provide or View instead of this.
func RemoveTag ¶
func RemoveTag[A any, Tag any](ball *Ball)
RemoveTag removes all the Tag type of tags from the component.
func RemoveTagOf ¶
func RemoveTagOf[TAG any](c *Component)
RemoveTagOf removes all the Tag type of tags from the component.
func ReplaceDependency ¶
func ReplaceDependency[BASE any, DEP any](ball *Ball)
ReplaceDependency replaces the dependency of a component. Can be used to switch to an alternative implementation.
func ReplaceDependencyOf ¶
func ReplaceDependencyOf(from *Component, to *Component)
ReplaceDependencyOf is like ReplaceDependency but using components instead of generics.
func RunWithDependencies ¶
func RunWithDependencies(ctx context.Context, ball *Ball, selector ComponentSelector) error
RunWithDependencies will init and run all components which are matched by the selector.
func Supply ¶
func Supply[T any](ball *Ball, t T)
Supply registers and instance which is already initialized.
func Tag ¶
func Tag[A any, Tag any](ball *Ball, tag Tag)
Tag attaches a tag to the component registration.
Types ¶
type Ball ¶
type Ball struct {
// contains filtered or unexported fields
}
Ball is the component registry.
Example ¶
Output:
type Component ¶
type Component struct {
// contains filtered or unexported fields
}
Component manages the lifecycle of a singleton Golang struct.
func Find ¶
func Find(ball *Ball, selector ComponentSelector) (result []*Component)
Find selects components matching the selector.
func FindSelectedWithDependencies ¶
func FindSelectedWithDependencies(ball *Ball, selector ComponentSelector) (result []*Component)
FindSelectedWithDependencies selects components matching the selector, together with all the dependencies.
func MustLookupComponent ¶
func MustLookupComponent[T any](ball *Ball) *Component
MustLookupComponent gets the component (or panics if doesn't exist) based on a type.
func (*Component) AddRequirement ¶ added in v1.122.1
func (c *Component) AddRequirement(in reflect.Type)
AddRequirement marks the argument type as dependency of this component.
func (*Component) Close ¶
func (c *Component) Close(ctx context.Context) error
Close calls the Close stage function.
func (*Component) GetTarget ¶
func (c *Component) GetTarget() reflect.Type
GetTarget returns with type, which is used as n identifier in mud.
func (*Component) Init ¶
func (c *Component) Init(ctx context.Context) error
Init initializes the internal singleton instance.
func (*Component) Instance ¶
func (c *Component) Instance() any
Instance returns the singleton instance of the component. Can be null, if not yet initialized.
func (*Component) Name ¶
func (c *Component) Name() string
Name returns with the human friendly name of the component.
type ComponentSelector ¶
type ComponentSelector func(c *Component) bool
ComponentSelector can filter components.
func And ¶
func And(selectors ...ComponentSelector) ComponentSelector
And selects components which matches all the selectors.
func ImplementationOf ¶
func ImplementationOf[L ~[]T, T any](ball *Ball) ComponentSelector
ImplementationOf is a ForEach filter to get all the dependency of an implementation.
type CustomDotNode ¶
type CustomDotNode interface {
// CustomizeDotNode can replace / modify entries, which are key=value parameters of graphviz dot.
CustomizeDotNode(tags []string) []string
}
CustomDotNode is an interface that can be implemented by a component to customize the SVG output for debugging..
type Injector ¶
type Injector[T any] func(ball *Ball, t reflect.Type) T
Injector is a function which can be used to inject a specific type.
type Interface ¶
type Interface struct {
}
Interface is a marker tag, to make it easier to list all possible extension points. Only used for debug / helper commands.
func (Interface) CustomizeDotNode ¶
func (i Interface) CustomizeDotNode(tags []string) []string
CustomizeDotNode customize the graphical representation of the node in the graph, when rendered for debugging.
type Nullable ¶
type Nullable struct {
}
Nullable is a custom tag, which enables injecting null value, even if the component is not initialized.
type Optional ¶
type Optional struct{}
Optional tag is used to mark components which may not required.
type Stage ¶
type Stage struct {
// contains filtered or unexported fields
}
Stage represents a function which should be called on the component at the right time (like start, stop, init).
type StageName ¶
type StageName string
StageName is the unique identifier of the stages (~lifecycle events).
type Wrapper ¶
type Wrapper struct {
// contains filtered or unexported fields
}
Wrapper can be used during injection to decorate existing instances.
func NewWrapper ¶
func NewWrapper[T any](f func(T) T) Wrapper
NewWrapper registers a new injection wrapper.