Architecture Overview¶
This document provides a high-level overview of CMDR's architecture and design principles.
System Overview¶
CMDR is a command-line tool built in Go that manages multiple versions of CLI commands. It uses a layered architecture with clear separation between:
- CLI Layer (
cmd/) - User-facing commands built with Cobra - Core Layer (
core/) - Business logic, interfaces, and abstractions - Storage Layer - BoltDB-based persistence via Storm ORM
┌─────────────────────────────────────────────────────────────┐
│ CLI Layer (cmd/) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌────────┐ │
│ │ init │ │ command │ │ config │ │ upgrade │ │ doctor │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ └───┬────┘ │
└───────┼──────────┼─────────┼─────────┼──────────┼───────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Core Layer (core/) │
│ ┌───────────────┐ ┌─────────────┐ ┌──────────────────┐ │
│ │ CommandManager│ │ Initializer │ │ Strategies │ │
│ │ Interface │ │ Interface │ │ (shim creation) │ │
│ └───────┬───────┘ └──────┬──────┘ └────────┬─────────┘ │
│ │ │ │ │
│ ┌───────┴───────┐ ┌──────┴──────┐ ┌────────┴─────────┐ │
│ │ Managers │ │ Initializers│ │ Fetchers │ │
│ │ (binary,db,..)│ │(profile,fs) │ │ (go-getter,go) │ │
│ └───────────────┘ └─────────────┘ └──────────────────┘ │
└───────────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Storage Layer │
│ ┌──────────────────┐ ┌─────────────────────────────┐ │
│ │ Storm/BoltDB │ │ Filesystem (shims, │ │
│ │ (cmdr.db) │ │ binaries, profile) │ │
│ └──────────────────┘ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Key Components¶
Entry Point¶
The application starts in main.go, which:
- Sets up panic recovery with custom exit codes1
- Publishes an exit event for cleanup
- Delegates to the CLI layer
// main.go L14-L33
func main() {
defer func() {
recovered := recover()
// Handle ExitError for clean exit codes
}()
defer core.PublishEvent(core.EventExit)
ctx := context.Background()
cmd.ExecuteContext(ctx)
}
CLI Layer (cmd/)¶
The CLI is built with Cobra and organized into subcommands:
| Command | Description | Source |
|---|---|---|
cmdr init |
Initialize CMDR environment | cmd/init.go |
cmdr command |
Manage command versions | cmd/command/ |
cmdr config |
Manage configuration | cmd/config/ |
cmdr upgrade |
Upgrade CMDR itself | cmd/upgrade.go |
cmdr doctor |
Diagnose issues | cmd/doctor.go |
cmdr version |
Show version info | cmd/version.go |
Core Layer (core/)¶
The core layer defines interfaces and provides implementations:
Interfaces¶
CommandManager- CRUD operations for commands2Initializer- System initialization tasks3CmdrSearcher- Finding CMDR releases4Database- Data persistence abstraction5
Implementations¶
Located in subdirectories:
core/manager/- Command manager implementations (binary, database, download, doctor)core/initializer/- Initialization implementations (filesystem, profile, command)core/fetcher/- File download implementations (go-getter, go)core/strategy/- Shim creation strategies (direct, proxy, chain, rewrite)
Configuration¶
Configuration is managed by Viper with support for:
- YAML configuration files
- Environment variables (prefixed with
CMDR_) - Command-line flags
Configuration initialization happens in cmd/root.go:
preInitConfig()- Set defaults and env binding6initConfig()- Load config file7postInitConfig()- Resolve relative paths8
Factory Pattern¶
CMDR uses the factory pattern extensively for extensibility:
// Example from core/command.go
var factoriesCommandManager map[CommandProvider]func(cfg Configuration) (CommandManager, error)
func RegisterCommandManagerFactory(key CommandProvider, fn func(...) (CommandManager, error)) {
factoriesCommandManager[key] = fn
}
func NewCommandManager(key CommandProvider, cfg Configuration) (CommandManager, error) {
fn, ok := factoriesCommandManager[key]
if !ok {
return nil, ErrCommandManagerFactoryeNotFound
}
return fn(cfg)
}
This pattern allows:
- Decoupled implementation registration
- Easy testing with mock implementations
- Runtime provider selection
Event System¶
CMDR uses an event bus for decoupled communication:
Events like EventExit allow components to clean up resources without tight coupling.
-
CommandManager interface in
core/command.goL36-L47 ↩ -
Initializer interface in
core/initializer.goL5-L7 ↩ -
CmdrSearcher interface in
core/cmdr.goL18-L20 ↩ -
Database interface in
core/database.go↩ -
Pre-initialization in
cmd/root.goL67-L89 ↩ -
Config loading in
cmd/root.goL92-L103 ↩ -
Path resolution in
cmd/root.goL105-L128 ↩