Filecoin 由一个个模块构成, 并采用 fx 实现各个模块间的依赖注入。

一般情况情况下,我们通过一个 New 函数构造一个对象,并通过函数参数方式手动传递各种依赖对象, 在一个大型的项目对象之间依赖关系变得非常复杂,如果我们手动的创建对象, 这是非常繁琐并且容易出错的。 而 fx 依赖框架可以为我们完成上面的工作。

fx 基础

具体细节可看: https://pkg.go.dev/go.uber.org/fx#example-package

fxconstructor 构造器任何能够有返回值的函数可以看作是一个 constructor, 因为其创建(返回)了一些对象。 constructor 类似于某些类型的 New*** 函数。

注意 constructor 可以同时返回一个多个对象。如果其中 一个对象的类型是 error 类型, 且值不为 nil 这时我们就知道在构建对象期间发生了错误, 此时就会停止构建。

我们启动一个程序过程通常如下:

  • 创建各种对象, 并构建我们的程序。
  • 开启一个后台的 goroutine 运行我们的程序。
  • 在程序退出进行一些善后工作, 也就是执行 clean() 函数。
1
2
3
4
5
6
7
srv, err := NewServer() 
if err != nil {
   log.Fatalf("failed to construct server: %v", err)
}

go srv.Start()
defer srv.Stop()

fx 框架将上面的过程抽象为 Lifeclye 。 并让程序启动阶段和结束阶段变成了 Hook 对象的 OnStartOnStop 函数了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
func NewMux(lc fx.Lifecycle, logger *log.Logger) *http.ServeMux {
	logger.Print("Executing NewMux.")
	mux := http.NewServeMux()

	server := &http.Server{
		Addr:    ":8080",
		Handler: mux,
	}
	lc.Append(fx.Hook{
		OnStart: func(context.Context) error {
			logger.Print("Starting HTTP server.")		
			go server.ListenAndServe()
			return nil
		},
		OnStop: func(ctx context.Context) error {
			logger.Print("Stopping HTTP server.")
			return server.Shutdown(ctx)
		},
	})

	return mux
}

在 fx 中用来当作依赖提供给别的对象,叫做 Provde, 你要构建的最终对象, 被称为 Invoke. 在创建 Fx 应用程序的时候, 你必须指定你提供的对象是 Provide 还是 Invoke.

1
2
3
4
5
6
7
8
9
app := fx.New(
	fx.Provide(
		NewLogger,
		NewHandler,
		NewMux,
	),
    fx.Popute(&impl.FullNode),
	fx.Invoke(Register),
)

Populate会填充结构体的字段各个字段。

filecoin 的依赖注入

在 filecoin 中有 ChainNode, MinerNode等角色的程序, 每个程序都包含若干个子系统,每个子系统之间相互独立, 也相互联系。 每个子系统都是我们的需要构建的目标, 相当于 fx.Invoke 对象, 这些子系统之间有构建顺序。 在 filecoin 中采用 setting 结构来容纳所有的构建对象, 最后并按照顺序传递 fx.App 让它按照顺序依次构建我们的子系统, 最后组成我们的程序。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
type Settings struct {
    // 模块或者子系统的依赖对象。 
	modules map[interface{}]fx.Option

    //子系统或者模块, 他们的返回类型不会作为其他模块或者对象的依赖对象。 这些模块必须按照顺序进行构建。
	invokes []fx.Option

    // 程序类型
	nodeType repo.RepoType

	Online bool // Online option applied
	Config bool // Config option applied
	Lite   bool // Start node in "lite" mode
}

下面是采用 fx 构建程序代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// New builds and starts new Filecoin node
func New(ctx context.Context, opts ...Option) (StopFunc, error) {
	settings := Settings{
		modules: map[interface{}]fx.Option{},
		invokes: make([]fx.Option, _nInvokes),
	}

	// apply module options in the right order
	if err := Options(Options(defaults()...), Options(opts...))(&settings); err != nil {
		return nil, xerrors.Errorf("applying node options failed: %w", err)
	}

	// gather constructors for fx.Options
	ctors := make([]fx.Option, 0, len(settings.modules))
	for _, opt := range settings.modules {
		ctors = append(ctors, opt)
	}

	// fill holes in invokes for use in fx.Options
	for i, opt := range settings.invokes {
		if opt == nil {
			settings.invokes[i] = fx.Options()
		}
	}

	app := fx.New(
		fx.Options(ctors...),
		fx.Options(settings.invokes...),

		fx.NopLogger,
	)

	// TODO: we probably should have a 'firewall' for Closing signal
	//  on this context, and implement closing logic through lifecycles
	//  correctly
	if err := app.Start(ctx); err != nil {
		// comment fx.NopLogger few lines above for easier debugging
		return nil, xerrors.Errorf("starting node: %w", err)
	}

	return app.Stop, nil
}