Using components¶
Using components within other components is covered on the create components page.
Now let's explore how to use components in your binaries. One of the core idea behind component design is to be able to create new binaries for customers by aggregating components.
the cmd
folder¶
All main
functions and binary entry points should be in the cmd
folder.
The cmd
folder uses the following hierarchy:
cmd /
<binary name> /
main.go <-- The entry points from your binary
subcommands / <-- All subcommand for your binary CLI
<subcommand name> / <-- The code specific to a single subcommand
command.go
command_test.go
Say you want to add a test
command to the agent
CLI.
You would create the following file:
package test
import (
// [...]
)
// Commands returns a slice of subcommands for the 'agent' command.
//
// The Agent uses "cobra" to create its CLI. The command method is your entrypoint. Here, you're going to create a single
// command.
func Commands(globalParams *command.GlobalParams) []*cobra.Command {
cmd := &cobra.Command{
Use: "test",
Short: "a test command for the Agent",
Long: ``,
RunE: func(_ *cobra.Command, _ []string) error {
return fxutil.OneShot(
<callback>,
<list of dependencies>.
)
},
}
return []*cobra.Command{cmd}
}
The code above creates a test command that does nothing. As you can see, fxutil.OneShot
helpers are being used. These helpers initialize an Fx app with all the wanted dependencies.
The next section explains how to request a dependency.
Importing components¶
The fxutil.OneShot
takes a list of components and gives them to Fx. Note that this only tells Fx how to create types when they're needed. This does not do anything else.
For a component to be instantiated, it must be one of the following:
- Required as a parameter by the
callback
function - Required as a dependency from other components already marked for instantiation
- Directly asked for by using
fx.Invoke
. More on this on the Fx page.
Let's require the log
components:
import (
// First let's import the FX wrapper to require it
logfx "github.com/DataDog/datadog-agent/comp/core/log/fx"
// Then the logger interface to use it
log "github.com/DataDog/datadog-agent/comp/core/log/def"
)
// [...]
return fxutil.OneShot(
myTestCallback, // The function to call from fxutil.OneShot
logfx.Module(), // This will tell FX how to create the `log.Component`
)
// [...]
func myTestCallback(logger log.Component) {
logger.Info("some message")
}
Importing bundles¶
Now let's say you want to include the core bundle instead. The core bundle offers many basic features (logger, config, telemetry, flare, ...).
import (
// We import the core bundle
core "github.com/DataDog/datadog-agent/comp/core"
// Then the interfaces we want to use
config "github.com/DataDog/datadog-agent/comp/core/config/def"
)
// [...]
return fxutil.OneShot(
myTestCallback, // The function to call from fxutil.OneShot
core.Bundle(), // This will tell FX how to create the all the components included in the bundle
)
// [...]
func myTestCallback(conf config.Component) {
api_key := conf.GetString("api_key")
// [...]
}
It's very important to understand that since myTestCallback
only uses the config.Component
, not all components from the core
bundle are instantiated! The core.Bundle
instructs Fx how to create components, but only the ones required are created.
In our example, the config.Component
might have dozens of dependencies instantiated from the core bundle. Fx handles all of this.
Using plain data types with Fx¶
As your migration to components is not finished, you might need to manually instruct Fx on how to use plain types.
You will need to use fx.Supply
for this. More details can be found here.
But here is a quick example:
import (
logfx "github.com/DataDog/datadog-agent/comp/core/log/fx"
log "github.com/DataDog/datadog-agent/comp/core/log/def"
)
// plain custom type
type custom struct {}
// [...]
return fxutil.OneShot(
myTestCallback,
logfx.Module(),
// fx.Supply populates values into Fx.
// Any time this is needed, Fx will use it.
fx.Supply(custom{})
)
// [...]
// Here our function uses component and non-component type, both provided by Fx.
func myTestCallback(logger log.Component, c custom) {
logger.Info("Custom type: %v", c)
}
Info
This means that components can depend on plain types too (as long as the main entry point populates Fx options with them).