Problem Statement
When building services with fx, there's a common pattern that's difficult to express cleanly:
- Injected dependencies (config, logger, database connections)
- Computed/derived fields (caches, connection pools)
- Internal state (mutexes, channels, maps)
Currently, fx's design constraints force verbose boilerplate for this pattern. Specifically:
The fx.In Constraints
- fx.In must be directly in params - Can't use embedded base structs that fx would need to flatten
- Return types cannot embed fx.In - fx/dig explicitly rejects:
"cannot provide parameter objects: embeds a dig.In"
- Anonymous embedding fails - Can't wrap params struct in return type
These constraints lead to tedious field-by-field copying:
type ServiceParams struct {
fx.In
Config *Config
Logger *Logger
DB *sql.DB
}
type Service struct {
config *Config
logger *Logger
db *sql.DB
cache *Cache // computed field
mu sync.Mutex // internal state
}
func NewService(p ServiceParams) *Service {
// Manual field-by-field copying
return &Service{
config: p.Config,
logger: p.Logger,
db: p.DB,
cache: NewCache(p.Config.CacheSize),
}
}
This becomes painful with 5+ dependencies and forces duplicate field definitions.
Two Key "Aha Moments"
While exploring solutions, two constraints stood out as potential improvement opportunities:
1. fx.In Cannot Appear in Return Types
This is a fundamental dig constraint that prevents the cleanest solution:
// ❌ This doesn't work - fx rejects: "cannot provide parameter objects: embeds a dig.In"
type ServiceParams struct {
fx.In
Config *Config
Logger *Logger
}
func NewService(p ServiceParams) *ServiceParams {
// Would love to just return the params with computed fields added
return &p
}
Question: Could fx support this with an annotation or tag? E.g., fx.In with fx:"allow-in-output" or a new fx.Partial marker?
2. Anonymous Embedding of Base Structs Fails
The most Go-idiomatic pattern would be:
// ❌ This doesn't work - fx can't flatten the embedded base
type ServiceBase struct {
fx.In // OR could be separate params struct
Config *Config
Logger *Logger
}
type Service struct {
ServiceBase // anonymous embedding for clean field access
cache *Cache
mu sync.Mutex
}
func NewService(base ServiceBase) *Service {
return &Service{
ServiceBase: base,
cache: NewCache(base.Config.Size),
}
}
Question: Could fx support recognizing anonymous embedded structs (with or without explicit tags) as "inject these fields"?
Use Case: Universal Pattern
While this came up during google/wire → uber/fx migration (wire's wire.Struct handled this elegantly), this is a general Go DI pattern:
Wire Context (for reference)
In wire, you could mark fields for partial initialization:
type Service struct {
Config *Config
Logger *Logger
Cache *Cache // manually initialized
}
// wire.Struct(new(Service), "Config", "Logger")
// Wire injects Config/Logger, user provides Cache
This pattern is common across many Go codebases:
- Services with lifecycle-managed resources
- Services with derived state from config
- Large microservice codebases with 10+ dependencies per service
- Library/module authors who want to provide fx.Module that consumers can include
What's Been Tried: Reflection-Based Solution
Through experimentation, I've developed a production-ready pattern using Go's reflection capabilities. This works beautifully but highlights opportunities for native fx support.
AutoConstructor Pattern (★ Recommended Approach)
The most idiomatic and composable solution:
// Define base with injected dependencies
type ServiceBase struct {
Config *Config
Logger *Logger
DB *sql.DB
}
// Define real struct with embedded base + internal state
type Service struct {
ServiceBase
cache map[string]any
mu sync.Mutex
}
// Auto-generate provider for ServiceBase
fx.Provide(AutoConstructor[ServiceBase]())
// Write normal constructor
func NewService(base ServiceBase) *Service {
return &Service{
ServiceBase: base,
cache: make(map[string]any),
}
}
fx.Provide(NewService)
With lifecycle hooks:
func NewService(base ServiceBase, lc fx.Lifecycle) *Service {
s := &Service{
ServiceBase: base,
cache: make(map[string]any),
}
lc.Append(fx.Hook{
OnStart: s.start,
OnStop: s.stop,
})
return s
}
In an fx.Module (library use case):
// Library code in github.com/myorg/mylib
var Module = fx.Module(
"MyLibrary",
fx.Provide(
AutoConstructor[ServerBase](),
NewServer,
),
)
// Consumer code
fx.New(
mylib.Module, // Just works!
// ...
)
Why this is the best pattern:
✅ No fx.In pollution - Base struct has no fx markers
✅ No fx:"-" tags - Clear separation: dependencies in Base, state in Real
✅ Normal constructors - Write regular Go functions, not reflection wrappers
✅ Composable - Multiple constructors can share the same Base
✅ Works in fx.Module - Can be packaged in libraries for reuse
✅ Natural lifecycle - fx.Lifecycle, fx.Shutdowner work normally in constructors
✅ Wire-like ergonomics - Mirrors wire.Struct conceptually
Alternative Explored: AutoProvidePartial + fx.Decorate
An alternative pattern uses fx:"-" tags and fx.Decorate:
type Service struct {
Config *Config // Injected by fx
Logger *Logger // Injected by fx
Cache *Cache `fx:"-"` // Manual initialization
State *State `fx:"-"` // Manual initialization
}
fx.Provide(AutoProvidePartial[Service]())
fx.Decorate(
func(s *Service, lc fx.Lifecycle) *Service {
s.Cache = NewCache(s.Config.CacheSize)
s.State = &State{Ready: false}
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
s.State.Ready = true
return nil
},
})
return s
},
)
Why this is less ideal:
❌ Cannot be packaged in fx.Module - fx.Decorate is scoped to the module it's in
❌ Library authors can't use it - You can provide AutoProvidePartial[Service]() in a module, but consumers would need to call fx.Decorate separately
❌ Not composable - Can't ship a complete library module that "just works"
The fundamental issue: fx.Decorate doesn't work across module boundaries. A library can't do:
// ❌ This doesn't work - Decorate is scoped to this module only
var Module = fx.Module(
"MyLibrary",
fx.Provide(AutoProvidePartial[Service]()),
fx.Decorate(func(s *Service) *Service {
s.Cache = NewCache(s.Config.Size)
return s
}),
)
The decorator only sees types provided within the same module, making this pattern unsuitable for library/module authors who want to ship complete, self-contained functionality.
This is why AutoConstructor is the superior pattern - it produces a complete, initialized object without requiring decorators.
Implementation Details: How Reflection Makes This Work
The core technique uses three reflection primitives to dynamically generate fx-compatible constructors:
func AutoConstructor[T any]() interface{} {
targetType := reflect.TypeOf((*T)(nil)).Elem()
// 1. reflect.StructOf - Build params struct with fx.In + exported fields
fields := []reflect.StructField{
{
Name: "In",
Type: reflect.TypeOf((*fx.In)(nil)).Elem(),
Anonymous: true,
},
}
numFields := targetType.NumField()
for i := 0; i < numFields; i++ {
field := targetType.Field(i)
if !field.IsExported() {
continue // Skip unexported fields
}
fields = append(fields, field)
}
paramsType := reflect.StructOf(fields)
// 2. reflect.FuncOf - Create function signature fx expects
returnType := targetType
if targetType.Kind() != reflect.Ptr {
returnType = reflect.TypeOf((*T)(nil))
}
funcType := reflect.FuncOf(
[]reflect.Type{paramsType},
[]reflect.Type{returnType},
false,
)
// 3. reflect.MakeFunc - Implement the function
fn := reflect.MakeFunc(funcType, func(args []reflect.Value) []reflect.Value {
paramsVal := args[0]
// Create target instance
var targetVal reflect.Value
if targetType.Kind() == reflect.Ptr {
targetVal = reflect.New(targetType.Elem())
} else {
targetVal = reflect.New(targetType).Elem()
}
// Copy fields from params to target
for i := 0; i < numFields; i++ {
field := targetType.Field(i)
if !field.IsExported() {
continue
}
// Find matching field in params (skip fx.In)
paramsField := paramsVal.FieldByName(field.Name)
targetField := targetVal
if targetType.Kind() == reflect.Ptr {
targetField = targetField.Elem()
}
targetField = targetField.FieldByName(field.Name)
targetField.Set(paramsField)
}
return []reflect.Value{targetVal}
})
return fn.Interface()
}
Key insight: fx's reflection can inspect dynamically-created types (from reflect.StructOf) as if they were hand-written structs. This lets us generate the params struct at runtime while keeping the target struct clean.
What this generates (conceptually):
// AutoConstructor[ServiceBase]() generates equivalent of:
type generatedParams struct {
fx.In
Config *Config
Logger *Logger
DB *sql.DB
}
func generatedConstructor(p generatedParams) ServiceBase {
return ServiceBase{
Config: p.Config,
Logger: p.Logger,
DB: p.DB,
}
}
Features that work:
- ✅ Standard fx struct tags: `name:"..."`, `optional:"true"`, `group:"..."`
- ✅ Error returns in constructors
- ✅ fx.Lifecycle, fx.Shutdowner injection
- ✅ Works in fx.Module
- ✅ Minimal runtime overhead (reflection only at startup)
Comparison: Current Workarounds
| Pattern |
Field Access |
Boilerplate |
Reflection |
Module-Safe |
Lifecycle |
Limitations |
| Manual copying |
Direct |
High |
None |
✅ |
✅ |
Duplicate definitions, verbose |
| Named field (.P.) |
service.P.Config |
Low |
None |
✅ |
✅ |
Awkward accessor |
| AutoConstructor |
Direct |
Minimal |
Startup only |
✅ |
✅ |
Needs external package, debug traces |
| AutoProvidePartial + Decorate |
Direct |
Minimal |
Startup only |
❌ |
✅ |
Cannot work in fx.Module |
All patterns work in production, but they require:
- External utility package
- Understanding of reflection internals
- Accepting `reflect.makeFuncStub` in stack traces
Potential Solutions (Open to fx Team's Design)
Several approaches could make this cleaner natively in fx:
Option A: Allow fx.In in Return Types (with annotation)
type Service struct {
fx.In `fx:"partial"` // or fx.Partial
Config *Config
Logger *Logger
cache *Cache `fx:"-"` // not injected
}
func NewService(s Service) *Service {
s.cache = NewCache(s.Config.Size)
return &s
}
Option B: Support Anonymous Base Struct Embedding
type ServiceBase struct {
fx.In // or could be implicit
Config *Config
Logger *Logger
}
type Service struct {
ServiceBase // fx recognizes and injects this
cache *Cache
}
func NewService(base ServiceBase) *Service {
return &Service{
ServiceBase: base,
cache: NewCache(base.Config.Size),
}
}
Option C: Native fx:"-" Tag Support
type ServiceParams struct {
fx.In
Config *Config
Logger *Logger
Cache *Cache \`fx:"-"\` // user provides this
}
type Service struct {
ServiceParams
}
func NewService(p ServiceParams) *Service {
p.Cache = NewCache(p.Config.Size)
return &Service{ServiceParams: p}
}
Option D: Official Reflection-Based Utility
Adopt the `AutoConstructor` pattern as an official fx utility:
import "go.uber.org/fx/fxutil"
fx.Provide(
fxutil.AutoConstructor[ServiceBase](),
NewService,
)
Or even simpler, a new marker type:
type ServiceBase struct {
fx.Struct // or fx.Base, fx.Injectable
Config *Config
Logger *Logger
}
// fx recognizes fx.Struct and auto-generates the constructor
Benefits of Native Support
- Better debugging - No `reflect.makeFuncStub` in stack traces
- Discoverability - Official documentation and examples
- Compile-time validation - Could provide better error messages
- No external dependencies - Pure fx solution
- Consistency - One blessed pattern instead of multiple workarounds
- Library/module authors - Can ship complete, self-contained modules
Evidence This Pattern is Needed
From my exploration:
- 3,000+ lines of production-ready code developed to solve this
- Multiple patterns explored (7+ variations)
- Comprehensive tests with lifecycle integration, error handling, edge cases
- Performance benchmarks showing minimal overhead
- Real-world usage in migration from wire
- Complete example module demonstrating library packaging
The AutoConstructor pattern has emerged as clearly superior because:
- Works in fx.Module (critical for library authors)
- Most idiomatic Go (normal constructors)
- No special tags or markers needed
- Composable across multiple services
Additional Context
- All struct tag features work on dynamically-created types (`name:"..."`, `optional:"true"`, `group:"..."`)
- Patterns are composable (multiple constructors can share same base)
- Works seamlessly with fx.Lifecycle, fx.Shutdowner, fx.Module
- Reflection overhead is startup-only (during fx.New), zero runtime cost
Happy to provide more details or examples if helpful!
What would you like to see? I'd love feedback on:
- Whether native support for this pattern makes sense for fx
- Which approach (A/B/C/D above) best fits fx's design philosophy
- Whether the reflection-based implementation could be adopted officially
Thank you for considering this enhancement!
Problem Statement
When building services with fx, there's a common pattern that's difficult to express cleanly:
Currently, fx's design constraints force verbose boilerplate for this pattern. Specifically:
The fx.In Constraints
"cannot provide parameter objects: embeds a dig.In"These constraints lead to tedious field-by-field copying:
This becomes painful with 5+ dependencies and forces duplicate field definitions.
Two Key "Aha Moments"
While exploring solutions, two constraints stood out as potential improvement opportunities:
1. fx.In Cannot Appear in Return Types
This is a fundamental dig constraint that prevents the cleanest solution:
Question: Could fx support this with an annotation or tag? E.g.,
fx.Inwithfx:"allow-in-output"or a newfx.Partialmarker?2. Anonymous Embedding of Base Structs Fails
The most Go-idiomatic pattern would be:
Question: Could fx support recognizing anonymous embedded structs (with or without explicit tags) as "inject these fields"?
Use Case: Universal Pattern
While this came up during
google/wire→uber/fxmigration (wire'swire.Structhandled this elegantly), this is a general Go DI pattern:Wire Context (for reference)
In wire, you could mark fields for partial initialization:
This pattern is common across many Go codebases:
What's Been Tried: Reflection-Based Solution
Through experimentation, I've developed a production-ready pattern using Go's reflection capabilities. This works beautifully but highlights opportunities for native fx support.
AutoConstructor Pattern (★ Recommended Approach)
The most idiomatic and composable solution:
With lifecycle hooks:
In an fx.Module (library use case):
Why this is the best pattern:
✅ No fx.In pollution - Base struct has no fx markers
✅ No fx:"-" tags - Clear separation: dependencies in Base, state in Real
✅ Normal constructors - Write regular Go functions, not reflection wrappers
✅ Composable - Multiple constructors can share the same Base
✅ Works in fx.Module - Can be packaged in libraries for reuse
✅ Natural lifecycle - fx.Lifecycle, fx.Shutdowner work normally in constructors
✅ Wire-like ergonomics - Mirrors wire.Struct conceptually
Alternative Explored: AutoProvidePartial + fx.Decorate
An alternative pattern uses
fx:"-"tags and fx.Decorate:Why this is less ideal:
❌ Cannot be packaged in fx.Module - fx.Decorate is scoped to the module it's in
❌ Library authors can't use it - You can provide
AutoProvidePartial[Service]()in a module, but consumers would need to call fx.Decorate separately❌ Not composable - Can't ship a complete library module that "just works"
The fundamental issue: fx.Decorate doesn't work across module boundaries. A library can't do:
The decorator only sees types provided within the same module, making this pattern unsuitable for library/module authors who want to ship complete, self-contained functionality.
This is why AutoConstructor is the superior pattern - it produces a complete, initialized object without requiring decorators.
Implementation Details: How Reflection Makes This Work
The core technique uses three reflection primitives to dynamically generate fx-compatible constructors:
Key insight: fx's reflection can inspect dynamically-created types (from
reflect.StructOf) as if they were hand-written structs. This lets us generate the params struct at runtime while keeping the target struct clean.What this generates (conceptually):
Features that work:
Comparison: Current Workarounds
All patterns work in production, but they require:
Potential Solutions (Open to fx Team's Design)
Several approaches could make this cleaner natively in fx:
Option A: Allow fx.In in Return Types (with annotation)
Option B: Support Anonymous Base Struct Embedding
Option C: Native fx:"-" Tag Support
Option D: Official Reflection-Based Utility
Adopt the `AutoConstructor` pattern as an official fx utility:
Or even simpler, a new marker type:
Benefits of Native Support
Evidence This Pattern is Needed
From my exploration:
The AutoConstructor pattern has emerged as clearly superior because:
Additional Context
Happy to provide more details or examples if helpful!
What would you like to see? I'd love feedback on:
Thank you for considering this enhancement!