Skip to content

Commit

Permalink
doc: Add FAQ page (#1160)
Browse files Browse the repository at this point in the history
Adds an FAQ page to the Fx documentation,
aiming to address frequently asked questions.

This includes two entries that came up at work recently.

Includes a test for the `fx.Supply` entry
because I couldn't find the test for it right away.
(I found it in annotated_test.go later but you won't hear me complain
about additional tests.)

---------

Co-authored-by: Jacob Oaks <[email protected]>
  • Loading branch information
abhinav and JacobOaks authored Feb 14, 2024
1 parent 793a644 commit d6beb49
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ module.exports = {
'modules.md',
],
},
['faq.md', 'FAQ'],
{
title: 'Community',
children: [
Expand Down
76 changes: 76 additions & 0 deletions docs/faq.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Frequently Asked Questions

This page contains answers to common questions and issues with using Fx.

## Does the order of `fx.Option`s matter?

No, the order in which you provide Fx options
to `fx.Options`, `fx.New`, `fx.Module`, and others does not matter.

Ordering of options relative to each other is as follows:

* Adding values:
Operations like `fx.Provide` and `fx.Supply` are run in dependency order.
Dependencies are determined by the function parameters and results.

```go
// The following are all equivalent:
fx.Options(fx.Provide(ParseConfig, NewLogger))
fx.Options(fx.Provide(NewLogger, ParseConfig))
fx.Options(fx.Provide(ParseConfig), fx.Provide(NewLogger))
fx.Options(fx.Provide(NewLogger), fx.Provide(ParseConfig))
```

* Consuming values:
Operations like `fx.Invoke` and `fx.Populate` are run
after their dependencies have been satisfied: after `fx.Provide`s.

Relative to each other, invokes are run in the order they were specified.

```go
fx.Invoke(a, b)
// a() is run before b()
```

`fx.Module` hierarchies affect invocation order:
invocations in a parent module are run after those of a child module.

```go
fx.Options(
fx.Invoke(a),
fx.Module("child", fx.Invoke(b)),
),
// b() is run before a()
```

* Replacing values:
Operations like `fx.Decorate` and `fx.Replace` are run
after the Provide operations that they depend on,
but before the Invoke operations that consume those values.

Ordering of decorations relative to each other
is determined by `fx.Module` hierarchies:
decorations in a parent module are applied after those of a child module.

## Why does `fx.Supply` not accept interfaces?

This is a technical limitation of how reflection in Go works.
Suppose you have:

```go
var redisClient ClientInterface = &redis.Client{ ... }
```

When you call `fx.Supply(redisClient)`,
the knowledge that you intended to use this as a `ClientInterface` is lost.
Fx has to use runtime reflection to inspect the type of the value,
and at that point the Go runtime only tells it that it’s a `*redis.Client`.

You can work around this with the `fx.Annotate` function
and the `fx.As` annotation.

```go
fx.Supply(
fx.Annotate(redisClient, fx.As(new(ClientInterface))),
)
```
21 changes: 21 additions & 0 deletions supply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
package fx_test

import (
"bytes"
"errors"
"io"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -110,6 +112,25 @@ func TestSupply(t *testing.T) {
require.Same(t, thirdIn, out.Third)
})

t.Run("AnnotateIsSupported", func(t *testing.T) {
t.Parallel()

var out struct {
fx.In

Got io.Writer
}

var give bytes.Buffer
app := fxtest.New(t,
fx.Supply(fx.Annotate(&give, fx.As(new(io.Writer)))),
fx.Populate(&out),
)
defer app.RequireStart().RequireStop()

require.Same(t, &give, out.Got)
})

t.Run("InvalidArgumentIsSupplied", func(t *testing.T) {
t.Parallel()

Expand Down

0 comments on commit d6beb49

Please sign in to comment.