A minimal example CLI application demonstrating Go interfaces and dependency injection patterns.
This project showcases how to structure a Go CLI application around small, behavior-driven interfaces. It manages a developer configuration file (storing a GitHub token) and serves as a reference for clean architecture principles.
- Initialize and manage a YAML configuration file
- Store and retrieve GitHub API tokens
- Prompt for secrets interactively when needed
- Fully testable architecture with pluggable implementations
go install github.com/juststeveking/config-example@latestOr clone and build locally:
git clone https://github.com/juststeveking/config-example
cd config-example
go build -o config-example main.goCreate or update the config file with an optional token:
config-example init --token your-github-tokenDisplay the current configuration:
config-example getUpdate the GitHub token (prompts if not provided):
config-example set [token].
├── cmd/ # CLI command implementations
│ ├── root.go # Root command and dependency setup
│ ├── init.go # Initialize config command
│ ├── get.go # Get config command
│ └── set.go # Set token command
├── internal/
│ ├── adapters/ # Concrete implementations
│ │ ├── os_paths.go # OS-based path resolver
│ │ ├── fs_store.go # File system store
│ │ ├── yaml.go # YAML serializer
│ │ └── prompt.go # stdin prompter
│ ├── app/ # Business logic
│ │ └── app.go # Service implementation
│ ├── config/ # Domain types
│ │ └── types.go # Config structures
│ └── ports/ # Interface definitions
│ └── interfaces.go # Port interfaces
├── main.go # Entry point
├── go.mod # Go module definition
├── go.sum # Dependency checksums
└── README.md # This file
This project demonstrates:
- Ports & Adapters: Small, focused interfaces (ports) that define capabilities, with concrete implementations (adapters) that handle I/O
- Dependency Injection: Interfaces are injected into the service, making it easy to swap implementations
- Test Seams: All external dependencies are abstracted, enabling deterministic unit tests without filesystem access
PathResolver: Determines where config files liveConfigStore: Handles file I/O operationsSerializer: Marshals/unmarshals config to/from bytesPrompter: Prompts for user input (secrets)
See article.md for a detailed walkthrough of the design patterns used.
The config file is stored at ~/.config/config-example/config.yml:
version: "1.0"
github:
token: "your-token-here"
repositories:
example-repo:
enabled: truego build -o config-example main.gogo test ./...go fmt ./...
go vet ./...github.com/spf13/cobra- CLI frameworkgithub.com/charmbracelet/fang- Enhanced error handlinggopkg.in/yaml.v3- YAML serialization
MIT
Feel free to fork and submit pull requests for any improvements!