Skip to content

Container

Container is the abstraction responsible for holding all constructors and values. It’s the primary means by which an application interacts with Fx. You teach the container about the needs of your application, how to perform certain operations, and then you let it handle actually running your application.

Fx does not provide direct access to the container. Instead, you specify operations to perform on the container by providing fx.Options to the fx.New constructor.

package fx

type App
  func New(opts ...Option) *App
  func (app *App) Run()

type Option
  func Provide(constructors ...interface{}) Option
  func Invoke(funcs ...interface{}) Option

Check the API Reference for a complete list of options and their behaviors.

Providing values

You must provide values to the container before you can use them. Fx provides two ways to provide values to the container:

  • fx.Provide for values that have a constructor.

    fx.Provide(
      func(cfg *Config) *Logger { /* ... */ },
    )
    

    This says that Fx should use this function to construct a *Logger, and that a *Config is required to build one.

  • fx.Supply for pre-built non-interface values.

    fx.Provide(
      fx.Supply(&Config{
        Name: "my-app",
      }),
    )
    

    This says that Fx should use the provided *Config as-is.

    Important: fx.Supply is only for non-interface values. See When to use fx.Supply for more details.

Values provided to the container are available to all other constructors. In the example above, the *Config would become available to the *Logger constructor, and the *Logger to any other constructors that need it.

When to use fx.Supply

Usually, fx.Provide is the right choice because more often than not, constructing an object requires its dependencies. fx.Supply is a convenience function for the rare cases where that isn't true: standalone values that don't depend on anything else.

fx.Provide(func() *Config { return &Config{Name: "my-app"} })
// is the same as
fx.Supply(&Config{Name: "my-app"})

However, even then, fx.Supply comes with a caveat: it can only be used for non-interface values.

Why can't I use fx.Supply for interface values?

This is a technical limitation imposed by the fact that fx.Supply has to rely on runtime reflection to determine the type of the value.

Passing an interface value to fx.Supply is a lossy operation: it loses the original interface type, only giving us interface{}, at which point reflection will only reveal the concrete type of the value.

For example, consider:

var svc RepositoryService = &repoService{ ... }

If you were to pass svc to fx.Supply, the container would only know that it's a *repoService, and it will not know that you intend to use it as a RepositoryService.

Using values

Providing values to the container only makes them available to the application. It doesn't do anything with them yet. Constructors passed to fx.Provide are not called until they are needed.

For example, the following won't do anything:

fx.New(
  fx.Provide(newHTTPServer), // provides an *http.Server
).Run()

You next have to tell the container what is needed, and what to do with it. Fx provides fx.Invoke for this purpose.

In the example above, we'll want an invocation that starts the server:

fx.New(
  fx.Provide(newHTTPServer),
  fx.Invoke(startHTTPServer),
).Run()

When to use fx.Invoke

fx.Invoke is typically used for root-level invocations, like starting a server or running a main loop. It's also useful for invoking functions that have side effects.

Examples of cases where you might use fx.Invoke:

  • Starting a background worker
  • Configuring a global logger

As an example, consider an application organized into many distinct abstractions.

flowchart LR
  CacheWarmer --> Redis
  Server[http.Server] --> UserHandler & PostHandler
  UserHandler --> Redis[redis.Client] & Client[http.Client]
  PostHandler --> sqlDB[sql.DB]

  subgraph Roots
    CacheWarmer
    Server
  end

CacheWarmer and http.Server are the roots of the application. We'll need fx.Invoke for the side effects of starting the server and the cache warmer loop. Everything else will be handled by the container automatically.