Go - uber/fx

XYMON·2023년 8월 10일

(WIP)

DI tool of GO.
Unlike other approaches to dependency injection, Fx works with plain Go functions: you don't need to use struct tags or embed special types, so Fx automatically works well with most Go packages.

How 'fx' achieves DI

Core of 'fx' is Provide and Invoke.

fx.Provide

Provider is a function that creates and returns an instance of 1 or more specific types/objs.
'fx.Provide' is used to register providers with the DI container.

fx.Invoke

Defining an action.
'fx' will automatically provide necessary dependencies of a func registered in invoke. Then, the func can use the dependencies to do something.
Functions are executed in the order they are registered.

When app starts, 'fx' builds a dependency graph and resolve all dependencies.
'fx' uses Go's reflection pkg to understand inputs of outputs of functions to wire.

Basic Example
config/config.go

package config

type Config struct {
	Content string
}

func New() Config {
	return Config{Content: "Hello world"}
}

New() is constructor function. Logger doesn't need other dependencies.
'fx' calls constructors lazily, so NewLogger is called only if some other function needs a logger.
Once it is instantiated, it is cahced and reused.(singleton)

package printer

import (
	"fmt"

	"myapp/config"
)

func Print(cfg config.Config) {
	fmt.Println(cfg.Content)
}

Print has formal parameters. 'fx' interpret this a s dependencies. To use Print, Config is needed. If it is cached in app already, 'fx' uses it.

func NewHandlerAndLogger() (*log.Logger, http.Handler, error)

Functions may return multiple objs like this.

main.go

package main

import (
	"log"
	"go.uber.org/fx"

	"myapp/config"
	"myapp/printer"
)

func main() {
	app := fx.New(
		fx.Provide(
			config.New,
		)
        fx.Invoke(
                printer.Print,
        ),
}

// result of ''go run main.go'
// Hello world

fx.Private

private option can be passed as an argument of Provide, to restrict access to the constructors being provided.
Private constructor can only be used within the current module or modules the current module contains.

fx.New(
	fx.Module("SubModule", fx.Provide(func() int { return 0 }, fx.Private)),
	fx.Invoke(func(a int) {}),
)
//error becuase cannot access to the int because it is private

fx.Annotate

Annotations are additional info that can be attached dependencies/providers to modify ther behavior or provide context. Tag, names etc.

fx.Provide(
    fx.Annotate(NewGateway, fx.ResultTag("custom-gateway")),
)

The name of annot - 'custom-gateway' can be used later to retreive or reference the provider.

Annotate lets you annotate a function's parameters and returns without you having to declare separate struct definitions for them.

type params struct {
  fx.In

  RO *db.Conn `name:"ro" optional:"true"`
  RW *db.Conn `name:"rw"`
}
type result struct {
  fx.Out

  GW *Gateway `name:"foo"`
}
fx.Provide(func(p params) result {
   return result{GW: NewGateway(p.RO, p.RW)}
})
// is equivalent to
func NewGateway(ro, rw *db.Conn) *Gateway { ... }
fx.Provide(
  fx.Annotate(
    NewGateway,
    // get a param with proper name tag
    fx.ParamTags(`name:"ro" optional:"true"`, `name:"rw"`),
    fx.ResultTags(`name:"foo"`),
  ),
  fx.Invoke(func(foo *Gateway) {
			fmt.Printf("ROConn Name: %s\n", foo.ROConn.Name)
			fmt.Printf("RWConn Name: %s\n", foo.RWConn.Name)
            }),
)

The tags can be used other provider or modules.

Variadic functions

fx.Annotate(func(mux *http.ServeMux, handlers ...http.Handler) {
  // ...
}, fx.ParamTags(``, `group:"server"`))

Annotated
Annotated annotates a constructor provided to Fx with additional options.

profile
염염

1개의 댓글

comment-user-thumbnail
2023년 8월 10일

즐겁게 읽었습니다. 유용한 정보 감사합니다.

답글 달기