Feature Setup & Registry¶
GTB uses a modular setup and registration pattern to decouple core framework logic from domain-specific features. This allows applications built with GTB to scale by adding new capabilities without modifying the central root command or initialization flow.
The Feature Registry¶
The FeatureRegistry (found in pkg/setup/registry.go) acts as a central clearinghouse for features to announce their presence. It manages three types of contributions:
- Initialisers: Code that runs during the
initcommand to configure a feature. - Subcommands: Cobra commands that should be added to the CLI hierarchy.
- Flags: Global or command-specific flags that a feature requires.
Self-Registration¶
Features register themselves using the Register function, typically called from a feature's package init() function or a high-level command constructor.
func init() {
setup.Register(
props.FeaturePipeline, // The unique feature identifier
[]setup.InitialiserProvider{NewPipelineInitialiser},
[]setup.SubcommandProvider{NewPipelineCommands},
nil, // No specific command flags
)
}
The Initialiser Interface¶
For features that require interactive setup (like configuring API keys or local paths), the Initialiser interface provides a standardized contract:
type Initialiser interface {
Name() string
IsConfigured(cfg config.Containable) bool
Configure(p *props.Props, cfg config.Containable) error
}
IsConfigured: Checks the existing configuration to see if setup can be skipped.Configure: Executes the interactive setup (often usinghuhor prompt libraries) and populates the configuration container.
The Init Workflow¶
When a user runs the init command, the setup.Initialise function performs the following steps:
- Bootstrap: Creates the default config directory and base
config.yaml. - Merge Assets: Loads any domain-specific configuration templates from the
Assetslayer. - Discovery: Retrieves all registered
InitialiserProviderfunctions from theglobalRegistry. - Execution: Iterates through each initialiser, checking if it's already configured and running the
Configurestep if necessary. - Persistence: Writes the final, merged configuration back to disk.
Why use this pattern?¶
- Decoupling: The core
rootandinitcommands don't need to know about every possible feature. They simply iterate through what has been registered. - Scalability: Adding a new feature is as simple as creating a new package that calls
setup.Register. - Consistency: All features follow the same setup and registration lifecycle, providing a predictable experience for both developers and users.
Best Practices¶
- Feature Enums: Define unique identifiers for your features in
pkg/propsor a shared constants package to avoid collisions. - Idempotent Setup: Ensure that
IsConfiguredaccurately reflects the state of the configuration to avoid re-prompting users for information they've already provided. - Asset Integration: If your feature requires default configuration values, include them in an
assets/init/config.yamlfile within your feature's package and register it as an asset.