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
- A sintaxe pode parecer pouco elegante em certas partes.
- A forma automática de obtenção de instâncias requer familiaridade.
- A injeção automática em funções construtoras pode ser mais amigável para desenvolvedores.