405 lines
11 KiB
Go
405 lines
11 KiB
Go
// Copyright (c) 2022 Uber Technologies, Inc.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
|
|
package fx
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"go.uber.org/dig"
|
|
"go.uber.org/fx/fxevent"
|
|
"go.uber.org/fx/internal/fxreflect"
|
|
"go.uber.org/multierr"
|
|
)
|
|
|
|
// A container represents a set of constructors to provide
|
|
// dependencies, and a set of functions to invoke once all the
|
|
// dependencies have been initialized.
|
|
//
|
|
// This definition corresponds to the dig.Container and dig.Scope.
|
|
type container interface {
|
|
Invoke(interface{}, ...dig.InvokeOption) error
|
|
Provide(interface{}, ...dig.ProvideOption) error
|
|
Decorate(interface{}, ...dig.DecorateOption) error
|
|
}
|
|
|
|
// Module is a named group of zero or more fx.Options.
|
|
//
|
|
// A Module scopes the effect of certain operations to within the module.
|
|
// For more information, see [Decorate], [Replace], or [Invoke].
|
|
//
|
|
// Module allows packages to bundle sophisticated functionality into easy-to-use
|
|
// logical units.
|
|
// For example, a logging package might export a simple option like this:
|
|
//
|
|
// package logging
|
|
//
|
|
// var Module = fx.Module("logging",
|
|
// fx.Provide(func() *log.Logger {
|
|
// return log.New(os.Stdout, "", 0)
|
|
// }),
|
|
// // ...
|
|
// )
|
|
//
|
|
// A shared all-in-one microservice package could use Module to bundle
|
|
// all required components of a microservice:
|
|
//
|
|
// package server
|
|
//
|
|
// var Module = fx.Module("server",
|
|
// logging.Module,
|
|
// metrics.Module,
|
|
// tracing.Module,
|
|
// rpc.Module,
|
|
// )
|
|
//
|
|
// When new global functionality is added to the service ecosystem,
|
|
// it can be added to the shared module with minimal churn for users.
|
|
//
|
|
// Use the all-in-one pattern sparingly.
|
|
// It limits the flexibility available to the application.
|
|
func Module(name string, opts ...Option) Option {
|
|
mo := moduleOption{
|
|
name: name,
|
|
location: fxreflect.CallerStack(1, 2)[0],
|
|
options: opts,
|
|
}
|
|
return mo
|
|
}
|
|
|
|
type moduleOption struct {
|
|
name string
|
|
location fxreflect.Frame
|
|
options []Option
|
|
}
|
|
|
|
func (o moduleOption) String() string {
|
|
return fmt.Sprintf("fx.Module(%q, %v)", o.name, o.options)
|
|
}
|
|
|
|
func (o moduleOption) apply(mod *module) {
|
|
// This get called on any submodules' that are declared
|
|
// as part of another module.
|
|
|
|
// 1. Create a new module with the parent being the specified
|
|
// module.
|
|
// 2. Apply child Options on the new module.
|
|
// 3. Append it to the parent module.
|
|
|
|
// Create trace as parent's trace with this module's location pre-pended.
|
|
trace := append([]string{fmt.Sprintf("%v (%v)", o.location, o.name)}, mod.trace...)
|
|
newModule := &module{
|
|
name: o.name,
|
|
parent: mod,
|
|
trace: trace,
|
|
app: mod.app,
|
|
}
|
|
for _, opt := range o.options {
|
|
opt.apply(newModule)
|
|
}
|
|
mod.modules = append(mod.modules, newModule)
|
|
}
|
|
|
|
type module struct {
|
|
parent *module
|
|
name string
|
|
trace []string
|
|
scope scope
|
|
provides []provide
|
|
invokes []invoke
|
|
decorators []decorator
|
|
modules []*module
|
|
app *App
|
|
log fxevent.Logger
|
|
fallbackLogger fxevent.Logger
|
|
logConstructor *provide
|
|
}
|
|
|
|
// scope is a private wrapper interface for dig.Container and dig.Scope.
|
|
// We can consider moving this into Fx using type constraints after Go 1.20
|
|
// is released and 1.17 is deprecated.
|
|
type scope interface {
|
|
Decorate(f interface{}, opts ...dig.DecorateOption) error
|
|
Invoke(f interface{}, opts ...dig.InvokeOption) error
|
|
Provide(f interface{}, opts ...dig.ProvideOption) error
|
|
Scope(name string, opts ...dig.ScopeOption) *dig.Scope
|
|
String() string
|
|
}
|
|
|
|
// builds the Scopes using the App's Container. Note that this happens
|
|
// after applyModules' are called because the App's Container needs to
|
|
// be built for any Scopes to be initialized, and applys' should be called
|
|
// before the Container can get initialized.
|
|
func (m *module) build(app *App, root *dig.Container) {
|
|
if m.parent == nil {
|
|
m.scope = root
|
|
} else {
|
|
parentScope := m.parent.scope
|
|
m.scope = parentScope.Scope(m.name)
|
|
// use parent module's logger by default
|
|
m.log = m.parent.log
|
|
}
|
|
|
|
if m.logConstructor != nil {
|
|
// Since user supplied a custom logger, use a buffered logger
|
|
// to hold all messages until user supplied logger is
|
|
// instantiated. Then we flush those messages after fully
|
|
// constructing the custom logger.
|
|
m.fallbackLogger, m.log = m.log, new(logBuffer)
|
|
}
|
|
|
|
for _, mod := range m.modules {
|
|
mod.build(app, root)
|
|
}
|
|
}
|
|
|
|
func (m *module) provideAll() {
|
|
for _, p := range m.provides {
|
|
m.provide(p)
|
|
}
|
|
|
|
for _, m := range m.modules {
|
|
m.provideAll()
|
|
}
|
|
}
|
|
|
|
func (m *module) provide(p provide) {
|
|
if m.app.err != nil {
|
|
return
|
|
}
|
|
|
|
if p.IsSupply {
|
|
m.supply(p)
|
|
return
|
|
}
|
|
|
|
funcName := fxreflect.FuncName(p.Target)
|
|
var info dig.ProvideInfo
|
|
opts := []dig.ProvideOption{
|
|
dig.FillProvideInfo(&info),
|
|
dig.Export(!p.Private),
|
|
dig.WithProviderCallback(func(ci dig.CallbackInfo) {
|
|
m.log.LogEvent(&fxevent.Run{
|
|
Name: funcName,
|
|
Kind: "provide",
|
|
ModuleName: m.name,
|
|
Err: ci.Error,
|
|
})
|
|
}),
|
|
}
|
|
|
|
if err := runProvide(m.scope, p, opts...); err != nil {
|
|
m.app.err = err
|
|
}
|
|
outputNames := make([]string, len(info.Outputs))
|
|
for i, o := range info.Outputs {
|
|
outputNames[i] = o.String()
|
|
}
|
|
|
|
m.log.LogEvent(&fxevent.Provided{
|
|
ConstructorName: funcName,
|
|
StackTrace: p.Stack.Strings(),
|
|
ModuleTrace: append([]string{p.Stack[0].String()}, m.trace...),
|
|
ModuleName: m.name,
|
|
OutputTypeNames: outputNames,
|
|
Err: m.app.err,
|
|
Private: p.Private,
|
|
})
|
|
}
|
|
|
|
func (m *module) supply(p provide) {
|
|
typeName := p.SupplyType.String()
|
|
opts := []dig.ProvideOption{
|
|
dig.Export(!p.Private),
|
|
dig.WithProviderCallback(func(ci dig.CallbackInfo) {
|
|
m.log.LogEvent(&fxevent.Run{
|
|
Name: fmt.Sprintf("stub(%v)", typeName),
|
|
Kind: "supply",
|
|
ModuleName: m.name,
|
|
})
|
|
}),
|
|
}
|
|
|
|
if err := runProvide(m.scope, p, opts...); err != nil {
|
|
m.app.err = err
|
|
}
|
|
|
|
m.log.LogEvent(&fxevent.Supplied{
|
|
TypeName: typeName,
|
|
StackTrace: p.Stack.Strings(),
|
|
ModuleTrace: append([]string{p.Stack[0].String()}, m.trace...),
|
|
ModuleName: m.name,
|
|
Err: m.app.err,
|
|
})
|
|
}
|
|
|
|
// Constructs custom loggers for all modules in the tree
|
|
func (m *module) constructAllCustomLoggers() {
|
|
if m.logConstructor != nil {
|
|
if buffer, ok := m.log.(*logBuffer); ok {
|
|
// default to parent's logger if custom logger constructor fails
|
|
if err := m.constructCustomLogger(buffer); err != nil {
|
|
m.app.err = multierr.Append(m.app.err, err)
|
|
m.log = m.fallbackLogger
|
|
buffer.Connect(m.log)
|
|
}
|
|
}
|
|
m.fallbackLogger = nil
|
|
} else if m.parent != nil {
|
|
m.log = m.parent.log
|
|
}
|
|
|
|
for _, mod := range m.modules {
|
|
mod.constructAllCustomLoggers()
|
|
}
|
|
}
|
|
|
|
// Mirroring the behavior of app.constructCustomLogger
|
|
func (m *module) constructCustomLogger(buffer *logBuffer) (err error) {
|
|
p := m.logConstructor
|
|
fname := fxreflect.FuncName(p.Target)
|
|
defer func() {
|
|
m.log.LogEvent(&fxevent.LoggerInitialized{
|
|
Err: err,
|
|
ConstructorName: fname,
|
|
})
|
|
}()
|
|
|
|
// TODO: Use dig.FillProvideInfo to inspect the provided constructor
|
|
// and fail the application if its signature didn't match.
|
|
if err := m.scope.Provide(p.Target); err != nil {
|
|
return fmt.Errorf("fx.WithLogger(%v) from:\n%+v\nin Module: %q\nFailed: %w",
|
|
fname, p.Stack, m.name, err)
|
|
}
|
|
|
|
return m.scope.Invoke(func(log fxevent.Logger) {
|
|
m.log = log
|
|
buffer.Connect(log)
|
|
})
|
|
}
|
|
|
|
func (m *module) executeInvokes() error {
|
|
for _, m := range m.modules {
|
|
if err := m.executeInvokes(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for _, invoke := range m.invokes {
|
|
if err := m.executeInvoke(invoke); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *module) executeInvoke(i invoke) (err error) {
|
|
fnName := fxreflect.FuncName(i.Target)
|
|
m.log.LogEvent(&fxevent.Invoking{
|
|
FunctionName: fnName,
|
|
ModuleName: m.name,
|
|
})
|
|
err = runInvoke(m.scope, i)
|
|
m.log.LogEvent(&fxevent.Invoked{
|
|
FunctionName: fnName,
|
|
ModuleName: m.name,
|
|
Err: err,
|
|
Trace: fmt.Sprintf("%+v", i.Stack), // format stack trace as multi-line
|
|
})
|
|
return err
|
|
}
|
|
|
|
func (m *module) decorateAll() error {
|
|
for _, d := range m.decorators {
|
|
if err := m.decorate(d); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for _, m := range m.modules {
|
|
if err := m.decorateAll(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *module) decorate(d decorator) (err error) {
|
|
if d.IsReplace {
|
|
return m.replace(d)
|
|
}
|
|
|
|
funcName := fxreflect.FuncName(d.Target)
|
|
var info dig.DecorateInfo
|
|
opts := []dig.DecorateOption{
|
|
dig.FillDecorateInfo(&info),
|
|
dig.WithDecoratorCallback(func(ci dig.CallbackInfo) {
|
|
m.log.LogEvent(&fxevent.Run{
|
|
Name: funcName,
|
|
Kind: "decorate",
|
|
ModuleName: m.name,
|
|
Err: ci.Error,
|
|
})
|
|
}),
|
|
}
|
|
|
|
err = runDecorator(m.scope, d, opts...)
|
|
outputNames := make([]string, len(info.Outputs))
|
|
for i, o := range info.Outputs {
|
|
outputNames[i] = o.String()
|
|
}
|
|
|
|
m.log.LogEvent(&fxevent.Decorated{
|
|
DecoratorName: funcName,
|
|
StackTrace: d.Stack.Strings(),
|
|
ModuleTrace: append([]string{d.Stack[0].String()}, m.trace...),
|
|
ModuleName: m.name,
|
|
OutputTypeNames: outputNames,
|
|
Err: err,
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
func (m *module) replace(d decorator) error {
|
|
typeName := d.ReplaceType.String()
|
|
opts := []dig.DecorateOption{
|
|
dig.WithDecoratorCallback(func(ci dig.CallbackInfo) {
|
|
m.log.LogEvent(&fxevent.Run{
|
|
Name: fmt.Sprintf("stub(%v)", typeName),
|
|
Kind: "replace",
|
|
ModuleName: m.name,
|
|
Err: ci.Error,
|
|
})
|
|
}),
|
|
}
|
|
|
|
err := runDecorator(m.scope, d, opts...)
|
|
m.log.LogEvent(&fxevent.Replaced{
|
|
ModuleName: m.name,
|
|
StackTrace: d.Stack.Strings(),
|
|
ModuleTrace: append([]string{d.Stack[0].String()}, m.trace...),
|
|
OutputTypeNames: []string{typeName},
|
|
Err: err,
|
|
})
|
|
return err
|
|
}
|