uber-go/fx

repository·master·Indexed 27 days ago

https://github.com/uber-go/fx

A dependency injection framework for Go that manages application lifecycles and dependencies without relying on global state. It provides tools for registering constructors via fx.Provide, supplying standalone values with fx.Supply, and managing startup and shutdown logic through lifecycle hooks like fx.OnStart and fx.OnStop. The framework includes advanced annotation capabilities via fx.Annotate for named or grouped values, interface casting with fx.As, and parameter remapping with fx.From.

Tokens
11.9K
Snippets
31
Records
86
Agent score
93%

What's inside fx

  1. Overview of Fx dependency injection

    master

    Fx is a dependency injection system for Go designed to manage application lifecycles and dependency graphs. It provides several key benefits for Go developers:

    • Eliminate globals: Use Fx-managed singletons to replace global state and init() functions, allowing Fx to manage the application lifecycle.
    • Reduce boilerplate: Define shared application setup in a single place and reuse it across multiple services.
    • Automatic plumbing: Fx automatically constructs the dependency graph. Once a component is added to the application, it can be used by any other component without manual configuration.
    • Code reuse: Build loosely-coupled, well-integrated, and shareable components known as modules.
  2. Understand Value Groups in Fx

    master

    A value group is a collection of values of the same type. It allows multiple constructors to contribute values to a single collection (e.g., []Route) without the consumers needing to know about every individual producer.

    Important: Fx produces values fed into a value group in a random order. Do not make any assumptions about the ordering of values within a group.

  3. Understand the Fx application lifecycle

    master

    An Fx application lifecycle consists of two main phases: initialization and execution.

    1. Initialization (triggered by fx.New)

    During this phase, Fx performs the following:

    • Registers all constructors provided via fx.Provide.
    • Registers all decorators provided via fx.Decorate.
    • Runs all functions provided via fx.Invoke, calling constructors and decorators as required to satisfy dependencies.

    2. Execution (triggered by fx.App.Run)

    During this phase, Fx performs the following:

    • Runs all startup hooks (appended by providers, decorators, or invoked functions).
    • Waits for a signal to stop running.
    • Runs all shutdown hooks.
  4. Best practices for Fx module design

    master

    To maintain clean and decoupled code, follow these design principles:

    • Don't provide what you don't own: Only provide types that are within your module's purview. Do not provide values you happen to use, and do not bundle other modules wholesale (unless creating a specific 'kitchen sink' module for an entire organization).
    • Keep independent modules thin: Modules with an fx suffix should rarely contain non-trivial business logic. This ensures business logic can be migrated to or from Fx without a rewrite.
    • Invoke sparingly: Use fx.Invoke deliberately. While fx.Provide only executes if the result is consumed, fx.Invoke runs unconditionally and instantiates every direct and transitive dependency.
  5. Configure Fx to use a custom logger

    master

    By default, Fx prints its own lifecycle and diagnostic messages to standard output. You can redirect these [Fx] messages to your own logging system by providing an fxevent.Logger.

    To ensure Fx logs follow your application's format, you can use fxevent.ZapLogger to wrap your existing Zap logger. This is typically done by providing a function to fx.Provide that constructs the fxevent.Logger using your application's logger.

  6. Use result objects in module functions

    master

    Functions exposed by a module should not declare their results as regular return values. Instead, use a result object.

    Rationale: This allows you to produce new results in a backwards-compatible manner without changing the function signature.

  7. Feed values to a value group using result objects

    master

    To feed a value of type T into a value group named $name, you can use a result object.

    1. Define a result object that contains an exported field of type T.
    2. Tag that field with group:"$name".
    3. In your provider function, populate this field with the value you want to feed into the group.
    4. Provide the function that returns the result object to the Fx application.
  8. Configure Strict vs Soft Value Groups

    master

    Fx allows you to control how dependencies within a value group affect the lifecycle of their producers. You can choose between strict and soft value groups.

    • Strict value groups (Default): The value group will always consume the values. Fx will run the necessary constructors to populate the group even if the producers' other return values are not used anywhere else in the application.
    • Soft value groups: The value group only consumes values if the constructors that produce them are already being invoked by the application for other reasons (i.e., their other return values are consumed directly or indirectly).
  9. Initialize an Fx application with fx.New

    master
    Fx uses a Container abstraction to manage constructors and values. You do not interact with the container directly; instead, you configure it by passing fx.Options to the fx.New constructor. To start the application, call the Run() method on the returned *App instance.
  10. Feed values to a value group using annotated functions

    master

    You can use fx.Annotate to send the result of an existing function to a value group without modifying the function itself.

    1. Take the function that produces the required value.
    2. Wrap the function call in fx.Provide with fx.Annotate.
    3. Use the group tag within fx.Annotate to specify the target value group name.

    Note on Type Casting: If the function produces a type that is not identical to the group's type but can be cast into it (e.g., a concrete struct being provided to a group of interfaces), you can still use fx.Annotate to feed it into the group.

  11. Annotate functions with fx.Annotate

    master

    Use fx.Annotate to wrap plain Go functions before passing them to fx.Provide, fx.Supply, fx.Invoke, fx.Decorate, or fx.Replace. This allows you to add metadata to functions without manually wrapping them in parameter or result objects. Common use cases include feeding or consuming values from value groups using tags.

    Prerequisites:

    • When using fx.ParamTags, the function must not already accept a parameter object.
    • When using fx.ResultTags, the function must not already return a result object.
  12. Extend an existing Result Object with new values

    master

    You can add new values to an existing result object in a backwards-compatible manner:

    1. Add a new exported field to the existing result object struct.
    2. Update the constructor to populate this new field before returning the struct.

    Existing consumers of the original fields will remain unaffected.