Compose dependency injection with Uber Fx

Rain Wu
Random Life Journal
4 min readOct 2, 2020

--

Consider a well-modulize project structure, we certainly take the advantage of the flexibility which empowers us to change or extend at less limit. Unfortunately, the complexity of communication between modules increases rapidly with the developing process.

Image from Unsplash

After a few version iterations, the engineers might start to think about related issues, for example:

  • “How many database connection handlers did we instantiate?”
  • “Which config object was read in this function?”
  • “I guess this exception was caused by the server initialized before the logger was ready…”

These all show the need for a clearer plan of interaction between instances, arrange the timing of initialization, and provide the entity to the receivers. Uber has released a lightweight tool called uber-go/fx as the solution, which implements the abstract concept of dependency injection, such as provide, receive, and OnStart event.

Dependencies

Image from Unsplash

Before we start to define the injection plan with fx, we should clarify the dependent relationship between all instances. Here I construct a simple project for practice here, the scope was all within a single service.

Dependencies Graph
  • Config:
    Load settings from environment variables and integrate with pre-defined constant into a config object.
  • Logger:
    Keep recording events occur over time into the log files.
  • Job Queue:
    Buffering the tasks in a queue instead of executing directly to avoid excessive loading.
  • DB Connector:
    Build the connection with the database and execute SQL commands, need to fetch the database host, username, and password from a Config.
  • Handler:
    Handle the request forward from the server, need a DB Connector for the database-related application.
  • Server:
    Keep listening to the port and deal with request-respond tasks, need to read a port number from a Config instance, forward requests to a Handler, record events via a Logger, and buffer tasks with a Job Queue.

Constructor

Due to the lack of polymorphism in Go, we can not define a very flexible construct function with the default parameter value. Instead of implementing countless constructors with a different parameter list, I think the Command Pattern is a better practice.

Here the construct function uses a variable-length argument to digest all kinds of option settings, just like implementing the polymorphism from another aspect. This pattern helps us pass only the option you want to specify, and others will use pre-defined value automatically.

Lifecycle

During the lifecycle of our service, the initialization of each module should be arranged reasonably according to the dependencies graph above.

The modules without any dependencies can be initialized first, like Config and Logger. But others like DBHandler should only be initialized after a Config instance is ready and passed in as a parameter.

Image from Unsplash

In uber’s fx, the function which provides an instance is called a provider and should be pass into the provide function. Some of them need other’s instance as a parameter, fx will make sure all required instances are ready and pass in with the corresponding type automatically, we do not need to worry about that.

Next, we need to register some tasks to be executed in the event of an fx application. According to the source code, it provides two events currently, OnStart will be invoked on the start time of application, and OnStop is on the stop time.

We can assign the starting processes to OnStart, such as “create tables if not exist in the database” and “the server starts listening to the port”. For some other ending tasks like “sync logger” and “stop server”, OnStop is what you need.

Recognizable log messages very help in process confirmation, these outputs describe the details of dependencies injection.

Conclusion

Fx is definitely an ease-to-use tool worth learning, a concise user interface can accomplish most of the tasks in a few functions. Although it may not be useful in a small to middle-scale project, such a kind of composer is necessary as architecture grows.

The complete code I mentioned above can be found on my GitHub repository, thanks for your reading :)

--

--

Rain Wu
Random Life Journal

A software engineer specializing in distributed systems and cloud services, desire to realize various imaginations of future life through technology.