Dependency Injection with Fx
Relevant source files
The following files were used as context for generating this wiki page:
The dokku-mcp server utilizes Uber's Fx, a dependency injection framework for Go. This framework is central to the application's architecture, responsible for initializing, wiring, and managing the lifecycle of all major components. It promotes a modular, decoupled, and testable codebase by constructing an application graph from a collection of providers and modules.
The core of the Fx integration is to define components (services, repositories, clients) as functions that return an object and its dependencies as parameters. Fx then resolves the dependency graph, instantiates the components in the correct order, and manages their startup and shutdown lifecycles. This is particularly crucial for the server's plugin-based architecture, where Fx dynamically assembles the active plugins.
Sources: pkg/fxapp/app.go, README.md
Application Startup and Fx Initialization
The application's entrypoint in cmd/server/main.go is minimal. Its sole responsibility is to instantiate and run the Fx application defined in the pkg/fxapp package.
// File: cmd/server/main.go:14-19
func main() {
// ... version flag handling ...
fxapp.New().Run()
}
Sources: cmd/server/main.go:14-19
The fxapp.New() function is where the entire application is composed. It gathers all the necessary modules and provides them to the fx.New() constructor. This function acts as the central manifest for the server's components.
This diagram illustrates the startup flow:
Sources: cmd/server/main.go:18, pkg/fxapp/app.go:15
Core Module Composition
The fxapp.New() function assembles a list of fx.Option elements, which are primarily fx.Module definitions. These modules group related providers.
// File: pkg/fxapp/app.go:28-38
return fx.New(
fxLogger,
fx.Supply(cfg),
config.Module,
logger.Module,
server.Module,
core.CoreModule,
domain.Module,
deployment.Module,
onboarding.Module,
app.Module,
)
The following diagram shows the high-level modules that constitute the application graph.
Sources: pkg/fxapp/app.go:28-38
Plugin Architecture with Fx Modules
The server's plugin system is built entirely on Fx modules. Each feature (e.g., app, deployment, core) is encapsulated in its own Go package and exposes an fx.Module. This module is responsible for providing all the components necessary for that feature to function.
Defining a Module
A module is defined as a package-level variable, typically named Module. It uses fx.Module to declare its name and a set of providers. The deployment module is a clear example of this pattern.
// File: internal/server-plugins/deployment/module.go:16-52
var Module = fx.Module("deployment",
fx.Provide(
// Deployment repository
fx.Annotate(
func(logger *slog.Logger) domain.DeploymentRepository {
return deployment_infrastructure.NewDeploymentRepository(logger)
},
),
// ... other providers ...
// Deployment service
fx.Annotate(
domain.NewApplicationDeploymentService,
fx.As(new(domain.DeploymentService)),
),
// Deployment adapter
fx.Annotate(
adapter.NewDeploymentServiceAdapter,
fx.As(new(shared.DeploymentService)),
),
),
)
Sources: internal/server-plugins/deployment/module.go:16-52
Component Providers and Annotations
Within a module, fx.Provide registers constructors with the Fx container. Fx automatically supplies the constructor's arguments by looking for other providers that can satisfy the required types.
For more complex scenarios, fx.Annotate is used to wrap a provider with additional options:
fx.As(new(interface)): Binds a concrete implementation to an interface. This allows other components to depend on the interface rather than the concrete type, promoting loose coupling.fx.ResultTags('group:"..."'): Adds the provider's result to a named group. This is the key mechanism for the dynamic plugin system.
The deployment module provides a comprehensive example of wiring together a feature's components, from the infrastructure layer up to the service layer.
This diagram shows the dependency graph for the deployment module as defined in its Fx providers.
This diagram illustrates the dependencies between components provided within the deployment module.
Sources: internal/server-plugins/deployment/module.go:17-51, PHASE1_IMPLEMENTATION.md
The following table summarizes the key components provided by the deployment module.
| Component | Provided As | Key Dependencies | Description |
|---|---|---|---|
NewDeploymentRepository | domain.DeploymentRepository | *slog.Logger | In-memory repository for storing deployment history. |
NewDeploymentTracker | *domain.DeploymentTracker | - | Thread-safe manager for in-memory tracking of active deployments. |
NewDeploymentStatusChecker | domain.DeploymentStatusChecker | dokkuApi.DokkuClient | Infrastructure component to query Dokku for deployment status. |
NewDeploymentPoller | *domain.DeploymentPoller | *DeploymentTracker, DeploymentStatusChecker | Background system for polling the status of active deployments. |
NewDeploymentInfrastructure | domain.DeploymentInfrastructure | DokkuClient, Logger, Tracker, Poller | Implements deployment operations by calling the Dokku client. |
NewApplicationDeploymentService | domain.DeploymentService | Repo, Tracker, Infra | The application service that orchestrates deployment logic. |
NewDeploymentServiceAdapter | shared.DeploymentService | DeploymentService | Adapts the deployment service to a shared interface for use by other plugins. |
Sources: internal/server-plugins/deployment/module.go:17-51, PHASE1_IMPLEMENTATION.md
Grouping Plugins with fx.ResultTags
The server discovers and registers all available plugins by using Fx groups. Each plugin that should be exposed by the server is provided with a result tag group:"server_plugins".
// File: internal/server-plugins/app/plugin.go:480-484
fx.Annotate(
NewAppsServerPlugin,
fx.As(new(domain.ServerPlugin)),
fx.ResultTags(`group:"server_plugins"`),
),
Sources: internal/server-plugins/app/plugin.go:480-484
Another component (e.g., a plugin registry) can then request a slice of all components in that group by using an in struct tag. Fx collects all providers tagged with group:"server_plugins" and injects them as a single slice []domain.ServerPlugin. This makes the plugin system extensible, as adding a new plugin is as simple as creating a new Fx module and adding it to the main application in fxapp.New().
The following diagram visualizes how different plugins are collected into a group.
This diagram shows how Fx uses result tags to collect multiple ServerPlugin implementations, which are then injected as a slice into a consuming component like the DynamicServerPluginRegistry.
Sources: internal/server-plugins/app/plugin.go:480-484, internal/server-plugins/core/plugin.go, internal/server-plugin/application/registry_test.go:133-136
Conclusion
Dependency injection with Uber's Fx is a foundational element of the dokku-mcp server's design. It provides a powerful mechanism for composing the application from modular, independent components. By leveraging fx.Module, fx.Provide, and annotations like fx.As and fx.ResultTags, the framework enables a clean separation of concerns, simplifies testing, and creates a highly extensible plugin architecture. This approach is critical for managing the complexity of the application and allowing new features to be integrated with minimal friction.