Using uber fx to provide an interface

Issue

I am attempting to use uber fx to do dependency injection for a go microservice project.

Since all the microservices will need to construct a base server, and set up a variety of config options (common middleware, buffer sizes etc) (I am using fiber). But these different microservices also have config options unique to the microservice. Maybe a database connection string, jwt keys etc.

I created an interface to use in the shared function that creates the common base app, with the common options, but any function needing a dependency of the config struct fails with expecting the specific version of config for that microservice.

failed to build *fiber.App: missing dependencies for function "some-path/http".CreateServer (some-path/http/http.go:65): missing type: *http.Config
exit status 1

Minimal example:

http/http.go

package http

import (
    "time"

    "github.com/gofiber/fiber/v2"
)


type BaseConfig interface {
    GetPort() string
    GetTimeout() int
}

type Config struct {
    Port           string `env:"LISTEN_ADDR" envDefault:":3000"`
    Timeout        uint64 `env:"TIMEOUT" envDefault:"10"`
}

func (c *Config) GetPort() string {
    return c.Port
}

func (c *Config) GetTimeout() int {
    return int(c.Timeout)
}

func CreateServer(config *Config) *fiber.App {
    fiberConfig := fiber.Config{
        ReadTimeout:    time.Second * time.Duration(config.GetTimeout()),
        WriteTimeout:   time.Second * time.Duration(config.GetTimeout()),
    }

    app := fiber.New(fiberConfig)

    // do setup and other stuff

    return app
}

some-service/config/config.go

package config

import (
    "github.com/caarlos0/env/v6"
    "github.com/rs/zerolog/log"
)

type Config struct {
    Port                string        `env:"LISTEN_ADDR" envDefault:":3000"`
    Timeout             uint64        `env:"TIMEOUT" envDefault:"10"`
    // some service specific stuff as well
}

func Parse() (*Config, error) {
    cfg := Config{}

    if err := env.Parse(&cfg); err != nil {
        return nil, err
    }

    return &cfg, nil
}

func (c *Config) GetPort() string {
    return c.Port
}

func (c *Config) GetTimeout() int {
    return int(c.Timeout)
}

some-service/main.go

package main

import (
    "context"
    "time"

    "some-path/http"
    "some-path/config"
    "some-path/controllers"
    "github.com/gofiber/fiber/v2"
    "go.uber.org/fx"
)

func main() {

    opts := []fx.Option{}
    opts = append(opts, provideOptions()...)
    opts = append(opts, fx.Invoke(run))

    app := fx.New(opts...)

    app.Run()
}

func provideOptions() []fx.Option {
    return []fx.Option{
        fx.Invoke(utils.ConfigureLogger),
        fx.Provide(config.Parse),
        fx.Invoke(controllers.SomeController),
    }
}

func run(app *fiber.App, config *config.Config, lc fx.Lifecycle) {
    lc.Append(fx.Hook{
        OnStart: func(ctx context.Context) error {
            errChan := make(chan error)

            go func() {
                errChan <- app.Listen(config.Port)
            }()

            select {
            case err := <-errChan:
                return err
            case <-time.After(100 * time.Millisecond):
                return nil
            }
        },
        OnStop: func(ctx context.Context) error {
            return app.Shutdown()
        },
    })
}

some-path/controllers/some-controller.go

package controllers

import "some-path/config"

func SomeController (config *config.Config) {
    // do stuff
}

Solution

You’re missing *http.Config object, create a function that return that object, e.g. NewConfig()

package http

import (
    "time"

    "github.com/caarlos0/env/v6"
    "github.com/gofiber/fiber/v2"
)

type BaseConfig interface {
    GetPort() string
    GetTimeout() int
}

type Config struct {
    Port    string `env:"LISTEN_ADDR" envDefault:":3000"`
    Timeout uint64 `env:"TIMEOUT" envDefault:"10"`
}

func NewConfig() (*Config, error) {
    cfg := Config{}

    if err := env.Parse(&cfg); err != nil {
        return nil, err
    }

    return &cfg, nil
}

func (c *Config) GetPort() string {
    return c.Port
}

func (c *Config) GetTimeout() int {
    return int(c.Timeout)
}

func CreateServer(config *Config) *fiber.App {
    fiberConfig := fiber.Config{
        ReadTimeout:  time.Second * time.Duration(config.GetTimeout()),
        WriteTimeout: time.Second * time.Duration(config.GetTimeout()),
    }

    app := fiber.New(fiberConfig)

    // do setup and other stuff

    return app
}

then change your provideOptions(), maybe like this:

func provideOptions() []fx.Option {
    return []fx.Option{
        fx.Invoke(utils.ConfigureLogger),
        fx.Provide(config.Parse, http.NewConfig),
        fx.Invoke(controllers.SomeController),
        fx.Provide(http.CreateServer),
    }
}

Answered By – Rahmat Fathoni

Answer Checked By – Marilyn (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.