Go for Tooling and CLI Utilities
Context
Prism uses Rust for the proxy (performance-critical path) and Python for orchestration. We need to choose a language for:
- CLI utilities and dev tools
- Repository management tools
- Data migration utilities
- Performance testing harnesses
- Potential backend adapters where appropriate
Requirements:
- Fast compilation for rapid iteration
- Single-binary distribution
- Good concurrency primitives
- Protobuf interoperability
- Cross-platform support
Decision
Use Go for tooling, CLI utilities, and select backend adapters.
Rationale
Why Go for Tooling
- Single Binary Distribution: No runtime dependencies, easy deployment
- Fast Compile Times: Rapid iteration during development
- Strong Standard Library: Excellent I/O, networking, and HTTP support
- Goroutines: Natural concurrency for parallel operations
- Protobuf Interoperability: First-class protobuf support, can consume same
.proto
files as Rust - Cross-Platform: Easy cross-compilation for Linux, macOS, Windows
Use Cases in Prism
Primary Use Cases (Go)
- CLI tools (
prism-cli
,prism-migrate
,prism-bench
) - Data migration utilities
- Load testing harnesses
- Repository analysis tools
- Backend health checkers
NOT Recommended (Use Rust Instead)
- Proxy core (latency-sensitive, use Rust)
- Hot-path request handlers (use Rust)
- Memory-intensive data processing (use Rust)
NOT Recommended (Use Python Instead)
- Code generation from protobuf (Python tooling established)
- Docker Compose orchestration (Python established)
- CI/CD scripting (Python established)
Alternatives Considered
-
Rust
- Pros: Maximum performance, memory safety, consistency with proxy
- Cons: Slower compile times, steeper learning curve for tools
- Rejected: Overkill for CLI tools, slower development velocity
-
Python
- Pros: Rapid development, rich ecosystem
- Cons: Slow execution, GIL limits concurrency, requires interpreter
- Rejected: Too slow for data migration/load testing
-
Node.js
- Pros: Fast async I/O, large ecosystem
- Cons: Memory overhead, callback complexity
- Rejected: Go's concurrency model clearer for our use cases
Consequences
Positive
- Fast compile times enable rapid iteration
- Single binaries simplify distribution (no Docker needed for tools)
- Strong concurrency primitives for parallel operations
- Shared protobuf definitions with Rust proxy
- Growing ecosystem for data infrastructure tools
Negative
- Another language in the stack (Rust, Python, Go)
- Different error handling patterns than Rust
- Garbage collection pauses (mitigated: not on hot path)
Neutral
- Learning curve for developers unfamiliar with Go
- Protobuf code generation required (standard across all languages)
Implementation Notes
Directory Structure
prism/ ├── tooling/ # Python orchestration (unchanged) ├── tools/ # Go CLI tools (new) │ ├── cmd/ │ │ ├── prism-cli/ # Main CLI tool │ │ ├── prism-migrate/ # Data migration │ │ └── prism-bench/ # Load testing │ ├── internal/ │ │ ├── config/ │ │ ├── proto/ # Generated Go protobuf code │ │ └── util/ │ ├── go.mod │ └── go.sum
### Key Libraries
// Protobuf import "google.golang.org/protobuf/proto"
// CLI framework import "github.com/spf13/cobra"
// Configuration import "github.com/spf13/viper"
// Structured logging import "log/slog"
// Concurrency patterns import "golang.org/x/sync/errgroup"
### Protobuf Sharing
Generate Go code from the same proto definitions:
Generate Go protobuf code
buf generate --template tools/buf.gen.go.yaml
`tools/buf.gen.go.yaml`:
version: v1 plugins:
- plugin: go
out: internal/proto
opt:
- paths=source_relative
- plugin: go-grpc
out: internal/proto
opt:
- paths=source_relative
### Example Tool: prism-cli
package main
import ( "context" "log/slog"
"github.com/spf13/cobra"
"github.com/prism/tools/internal/config"
"github.com/prism/tools/internal/proto/prism/keyvalue/v1"
)
var rootCmd = &cobra.Command{ Use: "prism-cli", Short: "Prism command-line interface", }
var getCmd = &cobra.Command{ Use: "get [namespace] [id] [key]", Short: "Get a value from Prism", Args: cobra.ExactArgs(3), RunE: func(cmd *cobra.Command, args []string) error { // Connect to proxy, issue Get request // ... return nil }, }
func main() { rootCmd.AddCommand(getCmd) if err := rootCmd.Execute(); err != nil { slog.Error("command failed", "error", err) os.Exit(1) } }
## References
- [Effective Go](https://go.dev/doc/effective_go)
- [Go Protobuf Documentation](https://protobuf.dev/reference/go/go-generated/)
- ADR-003: Protobuf as Single Source of Truth
- org-stream-producer ADR-001: Go for Git Analysis
## Revision History
- 2025-10-07: Initial draft and acceptance