Skip to content

Commit

Permalink
Fix Invoke order when fx.Module is used (uber-go#925)
Browse files Browse the repository at this point in the history
* Fix Invoke order when fx.Module is used

Currently, fx.Module's invokes are run after all the invokes provided
at the parent level are invoked.

This makes it difficult for module consumers to control the precise
invoke orders.

This changes the invoke order to run all the invokes in the child
module before running any of the invokes in the parent module, and
clarify that order in the documentation for Invoke.

Solves uber-go#918.
Refs GO-1591.

* fix indentation in code sample
  • Loading branch information
sywhang committed Oct 11, 2022
1 parent e4879e7 commit 2437807
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 4 deletions.
15 changes: 15 additions & 0 deletions invoke.go
Expand Up @@ -38,6 +38,21 @@ import (
// was successful.
// All other returned values are discarded.
//
// Invokes registered in [Module]s are run before the ones registered at the
// scope of the parent. Invokes within the same Module is run in the order
// they were provided. For example,
//
// fx.New(
// fx.Invoke(func3),
// fx.Module("someModule",
// fx.Invoke(func1),
// fx.Invoke(func2),
// ),
// fx.Invoke(func4),
// )
//
// invokes func1, func2, func3, func4 in that order.
//
// Typically, invoked functions take a handful of high-level objects (whose
// constructors depend on lower-level objects) and introduce them to each
// other. This kick-starts the application by forcing it to instantiate a
Expand Down
11 changes: 7 additions & 4 deletions module.go
Expand Up @@ -40,6 +40,8 @@ type container interface {
}

// Module is a named group of zero or more fx.Options.
// A Module creates a scope in which certain operations are taken
// place. For more information, see [Decorate], [Replace], or [Invoke].
func Module(name string, opts ...Option) Option {
mo := moduleOption{
name: name,
Expand Down Expand Up @@ -151,17 +153,18 @@ func (m *module) provide(p provide) {
}

func (m *module) executeInvokes() error {
for _, invoke := range m.invokes {
if err := m.executeInvoke(invoke); err != nil {
for _, m := range m.modules {
if err := m.executeInvokes(); err != nil {
return err
}
}

for _, m := range m.modules {
if err := m.executeInvokes(); err != nil {
for _, invoke := range m.invokes {
if err := m.executeInvoke(invoke); err != nil {
return err
}
}

return nil
}

Expand Down
30 changes: 30 additions & 0 deletions module_test.go
Expand Up @@ -211,6 +211,36 @@ func TestModuleSuccess(t *testing.T) {

defer app.RequireStart().RequireStop()
})

t.Run("Invoke order in Modules", func(t *testing.T) {
t.Parallel()

type person struct {
age int
}

app := fxtest.New(t,
fx.Provide(func() *person {
return &person{
age: 1,
}
}),
fx.Invoke(func(p *person) {
assert.Equal(t, 2, p.age)
p.age += 1
}),
fx.Module("module",
fx.Invoke(func(p *person) {
assert.Equal(t, 1, p.age)
p.age += 1
}),
),
fx.Invoke(func(p *person) {
assert.Equal(t, 3, p.age)
}),
)
require.NoError(t, app.Err())
})
}

func TestModuleFailures(t *testing.T) {
Expand Down

0 comments on commit 2437807

Please sign in to comment.