Análise do Código Fonte do fx

O fx é uma solução para Inversão de Controle e Montagem Automática na lingugaem Go, desenvolvida pela Uber.

Exemplo de Uso

Exemplo prático demonstrando a aplicação:

package main

import (
	"context"
	"log"
	"net/http"
	"os"
	"time"

	"go.uber.org/fx"
)

// NovoLogger constrói um logger. É uma função Go padrão, sem relacionamento especial com o fx.
// Como retorna um *log.Logger, o fx tratará NovoLogger como a função construtora para o logger
// da biblioteca padrão. O fx chama construtores de forma preguiçosa, então NovoLogger só será
// invocado se outra função necessitar de um logger. Uma vez instanciado, o logger é armazenado
// em cache e reutilizado, atuando como um singleton na aplicação.
func NovoLogger() *log.Logger {
	registrador := log.New(os.Stdout, "", 0)
	registrador.Print("Executando NovoLogger.")
	return registrador
}

// NovoManipulador constrói um manipulador HTTP simples. Como retorna um http.Handler,
// o fx o tratará como construtor para o tipo http.Handler. Além do manipulador, retorna
// um erro. Se o erro não for nulo, o fx assumirá que a construção falhou e os outros
// valores retornados não são seguros para uso.
func NovoManipulador(registrador *log.Logger) (http.Handler, error) {
	registrador.Print("Executando NovoManipulador.")
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		registrador.Print("Requisição recebida.")
	}), nil
}

// NovoMultiplexador constrói um multiplexador HTTP. Depende de *log.Logger e da interface
// Lifecycle do fx. A Lifecycle permite que objetos se conectem às fases de início e
// parada da aplicação. Os hooks são executados na ordem de dependência.
func NovoMultiplexador(lc fx.Lifecycle, registrador *log.Logger) *http.ServeMux {
	registrador.Print("Executando NovoMultiplexador.")
	mux := http.NewServeMux()
	servidor := &http.Server{
		Addr:    ":8080",
		Handler: mux,
	}
	lc.Append(fx.Hook{
		OnStart: func(ctx context.Context) error {
			registrador.Print("Iniciando servidor HTTP.")
			go servidor.ListenAndServe()
			return nil
		},
		OnStop: func(ctx context.Context) error {
			registrador.Print("Parando servidor HTTP.")
			return servidor.Shutdown(ctx)
		},
	})
	return mux
}

// Registrar monta nosso manipulador HTTP no multiplexador. Esta é uma função de invocação
// de alto nível: recebe um tipo genérico e o conecta à lógica da aplicação. As invocações
// são chamadas de forma eager, ao contrário dos construtores.
func Registrar(mux *http.ServeMux, h http.Handler) {
	mux.Handle("/", h)
}

func main() {
	aplicacao := fx.New(
		// Fornecer todos os construtores necessários, ensinando ao fx como construir os tipos.
		fx.Fornecer(
			NovoLogger,
			NovoManipulador,
			NovoMultiplexador,
		),
		// Invocar funções para iniciar a aplicação, pois construtores são chamados de forma preguiçosa.
		fx.Invocar(Registrar),
	)

	ctxInicio, cancelar := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancelar()
	if err := aplicacao.Start(ctxInicio); err != nil {
		log.Fatal(err)
	}

	http.Get("http://localhost:8080/")

	ctxParada, cancelar := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancelar()
	if err := aplicacao.Stop(ctxParada); err != nil {
		log.Fatal(err)
	}
}

Aálise do Código Principal

Métodos cetnrais:

fx.Provide (Fornecer)

func Provide(constructors ...interface{}) Option {
	return provideOption{
		Targets: constructors,
		Stack:   fxreflect.CallerStack(1, 0),
	}
}

fx.Invoke (Invocar)

func Invoke(funcs ...interface{}) Option {
	return invokeOption{
		Targets: funcs,
		Stack:   fxreflect.CallerStack(1, 0),
	}
}

fx.New (Novo)

func New(opts ...Option) *App {
	logger := fxlog.New()
	lc := &lifecycleWrapper{lifecycle.New(logger)}

	app := &App{
		lifecycle:    lc,
		logger:       logger,
		startTimeout: DefaultTimeout,
		stopTimeout:  DefaultTimeout,
	}

	for _, opt := range opts {
		opt.apply(app)
	}

	app.container = dig.New(
		dig.DeferAcyclicVerification(),
		dig.DryRun(app.validate),
	)

	for _, p := range app.provides {
		app.provide(p)
	}

	frames := fxreflect.CallerStack(0, 0)
	app.provide(provide{
		Target: func() Lifecycle { return app.lifecycle },
		Stack:  frames,
	})
	app.provide(provide{Target: app.shutdowner, Stack: frames})
	app.provide(provide{Target: app.dotGraph, Stack: frames})

	if app.err != nil {
		app.logger.Printf("Erro após a aplicação das opções: %v", app.err)
		return app
	}

	if err := app.executeInvokes(); err != nil {
		app.err = err

		if dig.CanVisualizeError(err) {
			var b bytes.Buffer
			dig.Visualize(app.container, &b, dig.VisualizeError(err))
			err = errorWithGraph{
				graph: b.String(),
				err:   err,
			}
		}
		errorHandlerList(app.errorHooks).HandleError(err)
	}
	return app
}

app.Start

func (app *App) Start(ctx context.Context) error {
	return withTimeout(ctx, app.start)
}

func (app *App) start(ctx context.Context) error {
	if app.err != nil {
		return app.err
	}

	if err := app.lifecycle.Start(ctx); err != nil {
		app.logger.Printf("ERRO\t\tFalha no início, revertendo: %v", err)
		if stopErr := app.lifecycle.Stop(ctx); stopErr != nil {
			app.logger.Printf("ERRO\t\tNão foi possível reverter limpaamente: %v", stopErr)
			return multierr.Append(err, stopErr)
		}
		return err
	}

	app.logger.Printf("EM EXECUÇÃO")
	return nil
}

Limitações Observadas

  1. A sintaxe pode parecer pouco elegante em certas partes.
  2. A forma automática de obtenção de instâncias requer familiaridade.
  3. A injeção automática em funções construtoras pode ser mais amigável para desenvolvedores.

Tags: fx Golang inversao-de-controle injecao-de-dependencia framework

Publicado em 6-19 05:26