|
| 1 | +# Repository Architecture |
| 2 | + |
| 3 | +This document describes the overall architecture and package organization of the StackState Backup CLI. |
| 4 | + |
| 5 | +## Design Philosophy |
| 6 | + |
| 7 | +The codebase follows several key principles: |
| 8 | + |
| 9 | +1. **Layered Architecture**: Dependencies flow from higher layers (commands) to lower layers (foundation utilities) |
| 10 | +2. **Self-Documenting Structure**: Directory hierarchy makes dependency rules and module purposes explicit |
| 11 | +3. **Clean Separation**: Domain logic, infrastructure, and presentation are clearly separated |
| 12 | +4. **Testability**: Lower layers can be tested independently without external dependencies |
| 13 | +5. **Reusability**: Shared functionality is extracted into appropriate packages |
| 14 | + |
| 15 | +## Repository Structure |
| 16 | + |
| 17 | +``` |
| 18 | +stackstate-backup-cli/ |
| 19 | +├── cmd/ # Command-line interface (Layer 3) |
| 20 | +│ ├── root.go # Root command and global flags |
| 21 | +│ ├── version/ # Version information command |
| 22 | +│ ├── elasticsearch/ # Elasticsearch backup/restore commands |
| 23 | +│ └── stackgraph/ # Stackgraph backup/restore commands |
| 24 | +│ |
| 25 | +├── internal/ # Internal packages (Layers 0-2) |
| 26 | +│ ├── foundation/ # Layer 0: Core utilities |
| 27 | +│ │ ├── config/ # Configuration management |
| 28 | +│ │ ├── logger/ # Structured logging |
| 29 | +│ │ └── output/ # Output formatting |
| 30 | +│ │ |
| 31 | +│ ├── clients/ # Layer 1: Service clients |
| 32 | +│ │ ├── k8s/ # Kubernetes client |
| 33 | +│ │ ├── elasticsearch/ # Elasticsearch client |
| 34 | +│ │ └── s3/ # S3/Minio client |
| 35 | +│ │ |
| 36 | +│ ├── orchestration/ # Layer 2: Workflows |
| 37 | +│ │ ├── portforward/ # Port-forwarding orchestration |
| 38 | +│ │ └── scale/ # Deployment scaling workflows |
| 39 | +│ │ |
| 40 | +│ └── scripts/ # Embedded bash scripts |
| 41 | +│ |
| 42 | +├── main.go # Application entry point |
| 43 | +├── ARCHITECTURE.md # This file |
| 44 | +└── README.md # User documentation |
| 45 | +``` |
| 46 | + |
| 47 | +## Architectural Layers |
| 48 | + |
| 49 | +### Layer 3: Commands (`cmd/`) |
| 50 | + |
| 51 | +**Purpose**: User-facing CLI commands and application entry points |
| 52 | + |
| 53 | +**Characteristics**: |
| 54 | +- Implements the Cobra command structure |
| 55 | +- Handles user input validation and flag parsing |
| 56 | +- Orchestrates calls to lower layers |
| 57 | +- Formats output for end users |
| 58 | + |
| 59 | +**Key Packages**: |
| 60 | +- `cmd/elasticsearch/`: Elasticsearch snapshot/restore commands (configure, list-snapshots, list-indices, restore-snapshot) |
| 61 | +- `cmd/stackgraph/`: Stackgraph backup/restore commands (list, restore) |
| 62 | +- `cmd/version/`: Version information |
| 63 | + |
| 64 | +**Dependency Rules**: |
| 65 | +- ✅ Can import: All `internal/` packages |
| 66 | +- ❌ Should not: Contain business logic or direct service calls |
| 67 | + |
| 68 | +### Layer 2: Orchestration (`internal/orchestration/`) |
| 69 | + |
| 70 | +**Purpose**: High-level workflows that coordinate multiple services |
| 71 | + |
| 72 | +**Characteristics**: |
| 73 | +- Composes multiple clients to implement complex workflows |
| 74 | +- Handles sequencing and error recovery |
| 75 | +- Provides logging and user feedback |
| 76 | +- Stateless operations |
| 77 | + |
| 78 | +**Key Packages**: |
| 79 | +- `portforward/`: Manages Kubernetes port-forwarding lifecycle |
| 80 | +- `scale/`: Deployment scaling workflows with detailed logging |
| 81 | + |
| 82 | +**Dependency Rules**: |
| 83 | +- ✅ Can import: `internal/foundation/*`, `internal/clients/*` |
| 84 | +- ❌ Cannot import: Other `internal/orchestration/*` (to prevent circular dependencies) |
| 85 | + |
| 86 | +### Layer 1: Clients (`internal/clients/`) |
| 87 | + |
| 88 | +**Purpose**: Wrappers for external service APIs |
| 89 | + |
| 90 | +**Characteristics**: |
| 91 | +- Thin abstraction over external APIs |
| 92 | +- Handles connection and authentication |
| 93 | +- Translates between external formats and internal types |
| 94 | +- No business logic or orchestration |
| 95 | + |
| 96 | +**Key Packages**: |
| 97 | +- `k8s/`: Kubernetes API operations (Jobs, Pods, Deployments, ConfigMaps, Secrets, Logs) |
| 98 | +- `elasticsearch/`: Elasticsearch HTTP API (snapshots, indices, datastreams) |
| 99 | +- `s3/`: S3/Minio operations (client creation, object filtering) |
| 100 | + |
| 101 | +**Dependency Rules**: |
| 102 | +- ✅ Can import: `internal/foundation/*`, standard library, external SDKs |
| 103 | +- ❌ Cannot import: `internal/orchestration/*`, other `internal/clients/*` |
| 104 | + |
| 105 | +### Layer 0: Foundation (`internal/foundation/`) |
| 106 | + |
| 107 | +**Purpose**: Core utilities with no internal dependencies |
| 108 | + |
| 109 | +**Characteristics**: |
| 110 | +- Pure utility functions |
| 111 | +- No external service dependencies |
| 112 | +- Broadly reusable across the application |
| 113 | +- Well-tested and stable |
| 114 | + |
| 115 | +**Key Packages**: |
| 116 | +- `config/`: Configuration loading from ConfigMaps, Secrets, environment, and flags |
| 117 | +- `logger/`: Structured logging with levels (Debug, Info, Warning, Error, Success) |
| 118 | +- `output/`: Output formatting (tables, JSON, YAML, messages) |
| 119 | + |
| 120 | +**Dependency Rules**: |
| 121 | +- ✅ Can import: Standard library, external utility libraries |
| 122 | +- ❌ Cannot import: Any `internal/` packages |
| 123 | + |
| 124 | +## Data Flow |
| 125 | + |
| 126 | +### Typical Command Execution Flow |
| 127 | + |
| 128 | +``` |
| 129 | +1. User invokes CLI command |
| 130 | + └─> cmd/elasticsearch/restore-snapshot.go |
| 131 | + │ |
| 132 | +2. Parse flags and validate input |
| 133 | + │ |
| 134 | +3. Load configuration |
| 135 | + └─> internal/foundation/config/ |
| 136 | + │ |
| 137 | +4. Create clients |
| 138 | + └─> internal/clients/k8s/ |
| 139 | + └─> internal/clients/elasticsearch/ |
| 140 | + │ |
| 141 | +5. Execute orchestration workflow |
| 142 | + └─> internal/orchestration/scale/ |
| 143 | + └─> Scale down deployments |
| 144 | + └─> internal/orchestration/portforward/ |
| 145 | + └─> Setup port-forward to Elasticsearch |
| 146 | + └─> internal/clients/elasticsearch/ |
| 147 | + └─> Perform snapshot restore |
| 148 | + └─> internal/orchestration/scale/ |
| 149 | + └─> Scale up deployments |
| 150 | + │ |
| 151 | +6. Format and display results |
| 152 | + └─> internal/foundation/output/ |
| 153 | +``` |
| 154 | + |
| 155 | +## Key Design Patterns |
| 156 | + |
| 157 | +### 1. Configuration Precedence |
| 158 | + |
| 159 | +Configuration is loaded with the following precedence (highest to lowest): |
| 160 | + |
| 161 | +1. **CLI Flags**: Explicit user input |
| 162 | +2. **Environment Variables**: Runtime configuration |
| 163 | +3. **Kubernetes Secret**: Sensitive credentials (overrides ConfigMap) |
| 164 | +4. **Kubernetes ConfigMap**: Base configuration |
| 165 | +5. **Defaults**: Fallback values |
| 166 | + |
| 167 | +Implementation: `internal/foundation/config/config.go` |
| 168 | + |
| 169 | +### 2. Client Factory Pattern |
| 170 | + |
| 171 | +Clients are created with a consistent factory pattern: |
| 172 | + |
| 173 | +```go |
| 174 | +// Example from internal/clients/elasticsearch/client.go |
| 175 | +func NewClient(endpoint string) (*Client, error) { |
| 176 | + // Initialization logic |
| 177 | + return &Client{...}, nil |
| 178 | +} |
| 179 | +``` |
| 180 | + |
| 181 | +### 3. Port-Forward Lifecycle |
| 182 | + |
| 183 | +Services running in Kubernetes are accessed via automatic port-forwarding: |
| 184 | + |
| 185 | +```go |
| 186 | +// Example from internal/orchestration/portforward/portforward.go |
| 187 | +pf, err := SetupPortForward(k8sClient, namespace, service, localPort, remotePort, log) |
| 188 | +defer close(pf.StopChan) // Automatic cleanup |
| 189 | +``` |
| 190 | + |
| 191 | +### 4. Scale Down/Up Pattern |
| 192 | + |
| 193 | +Deployments are scaled down before restore operations and scaled up afterward: |
| 194 | + |
| 195 | +```go |
| 196 | +// Example usage |
| 197 | +scaledDeployments, _ := scale.ScaleDown(k8sClient, namespace, selector, log) |
| 198 | +defer scale.ScaleUp(k8sClient, namespace, scaledDeployments, log) |
| 199 | +``` |
| 200 | + |
| 201 | +### 5. Structured Logging |
| 202 | + |
| 203 | +All operations use structured logging with consistent levels: |
| 204 | + |
| 205 | +```go |
| 206 | +log.Infof("Starting operation...") |
| 207 | +log.Debugf("Detail: %v", detail) |
| 208 | +log.Warningf("Non-fatal issue: %v", warning) |
| 209 | +log.Errorf("Operation failed: %v", err) |
| 210 | +log.Successf("Operation completed successfully") |
| 211 | +``` |
| 212 | + |
| 213 | +## Testing Strategy |
| 214 | + |
| 215 | +### Unit Tests |
| 216 | +- **Location**: Same directory as source (e.g., `config_test.go`) |
| 217 | +- **Focus**: Business logic, parsing, validation |
| 218 | +- **Mocking**: Use interfaces for external dependencies |
| 219 | + |
| 220 | +### Integration Tests |
| 221 | +- **Location**: `cmd/*/` directories |
| 222 | +- **Focus**: Command execution with mocked Kubernetes |
| 223 | +- **Tools**: `fake.NewSimpleClientset()` from `k8s.io/client-go` |
| 224 | + |
| 225 | +### End-to-End Tests |
| 226 | +- **Status**: Not yet implemented |
| 227 | +- **Future**: Use `kind` or `k3s` for local Kubernetes cluster testing |
| 228 | + |
| 229 | +## Extending the Codebase |
| 230 | + |
| 231 | +### Adding a New Command |
| 232 | + |
| 233 | +1. Create command file in `cmd/<service>/` |
| 234 | +2. Implement Cobra command structure |
| 235 | +3. Use existing clients or create new ones in `internal/clients/` |
| 236 | +4. Implement workflow in `internal/orchestration/` if needed |
| 237 | +5. Add tests following existing patterns |
| 238 | + |
| 239 | +### Adding a New Client |
| 240 | + |
| 241 | +1. Create package in `internal/clients/<service>/` |
| 242 | +2. Implement client factory: `NewClient(...) (*Client, error)` |
| 243 | +3. Only import `internal/foundation/*` packages |
| 244 | +4. Add methods for each API operation |
| 245 | +5. Write unit tests with mocked HTTP/API calls |
| 246 | + |
| 247 | +### Adding a New Orchestration Workflow |
| 248 | + |
| 249 | +1. Create package in `internal/orchestration/<workflow>/` |
| 250 | +2. Import required clients from `internal/clients/*` |
| 251 | +3. Import utilities from `internal/foundation/*` |
| 252 | +4. Keep workflows stateless |
| 253 | +5. Add comprehensive logging |
| 254 | + |
| 255 | +## Common Pitfalls to Avoid |
| 256 | + |
| 257 | +### ❌ Don't: Import Clients from Other Clients |
| 258 | + |
| 259 | +```go |
| 260 | +// BAD: internal/clients/elasticsearch/backup.go |
| 261 | +import "github.com/.../internal/clients/k8s" // Violates layer rules |
| 262 | +``` |
| 263 | + |
| 264 | +**Fix**: Move the orchestration logic to `internal/orchestration/` |
| 265 | + |
| 266 | +### ❌ Don't: Put Business Logic in Commands |
| 267 | + |
| 268 | +```go |
| 269 | +// BAD: cmd/elasticsearch/restore.go |
| 270 | +func runRestore() { |
| 271 | + // 200 lines of business logic here |
| 272 | +} |
| 273 | +``` |
| 274 | + |
| 275 | +**Fix**: Extract logic to orchestration or client packages |
| 276 | + |
| 277 | +### ❌ Don't: Import Foundation Packages from Each Other |
| 278 | + |
| 279 | +```go |
| 280 | +// BAD: internal/foundation/config/loader.go |
| 281 | +import "github.com/.../internal/foundation/output" |
| 282 | +``` |
| 283 | + |
| 284 | +**Fix**: Foundation packages should be independent |
| 285 | + |
| 286 | +### ❌ Don't: Hard-code Configuration |
| 287 | + |
| 288 | +```go |
| 289 | +// BAD |
| 290 | +endpoint := "http://localhost:9200" |
| 291 | +``` |
| 292 | + |
| 293 | +**Fix**: Use configuration management: `config.Elasticsearch.Service.Name` |
| 294 | + |
| 295 | +## Automated Enforcement |
| 296 | + |
| 297 | +Verify architectural rules with these commands: |
| 298 | + |
| 299 | +```bash |
| 300 | +# Verify foundation/ has no internal/ imports |
| 301 | +go list -f '{{.ImportPath}}: {{join .Imports "\n"}}' ./internal/foundation/... | \ |
| 302 | + grep 'stackvista.*internal' |
| 303 | + |
| 304 | +# Verify clients/ only imports foundation/ |
| 305 | +go list -f '{{.ImportPath}}: {{join .Imports "\n"}}' ./internal/clients/... | \ |
| 306 | + grep 'stackvista.*internal' | grep -v foundation |
| 307 | + |
| 308 | +# Verify orchestration/ doesn't import other orchestration/ |
| 309 | +go list -f '{{.ImportPath}}: {{join .Imports "\n"}}' ./internal/orchestration/... | \ |
| 310 | + grep 'stackvista.*orchestration' |
| 311 | +``` |
0 commit comments