refactor(structure): create DDD directory structure
- commit
- 9723233cf4a6703ef58b6a005a19b451d2ee5139
- author
- bnema · · 2025-10-31 10:43 UTC
- parent
- bd3af61
- signature
- GPG Unverified
2B82207D370127A0The key is unknown
27 files · +391 -0
A docs/architecture.md +391 -0
| @@ -0,0 +1,391 @@ | |||
| 1 | + | # ArchUp Architecture - Domain-Driven Design (DDD) | |
| 2 | + | ||
| 3 | + | ## Overview | |
| 4 | + | ||
| 5 | + | ArchUp has been restructured using Domain-Driven Design (DDD) principles to achieve: | |
| 6 | + | - Better testability (domain logic tested without infrastructure) | |
| 7 | + | - Clear separation of concerns (business logic isolated from infrastructure) | |
| 8 | + | - Improved maintainability (easy to extend without modifying existing code) | |
| 9 | + | - Reduced coupling (dependencies point inward) | |
| 10 | + | ||
| 11 | + | ## Core Principles | |
| 12 | + | ||
| 13 | + | 1. **Business logic ONLY in domain** - NO external dependencies (no OS, HTTP, DB, etc.) | |
| 14 | + | 2. **Domain defines interfaces (Ports)** - Infrastructure implements them (Adapters) | |
| 15 | + | 3. **Dependencies point INWARD** - Everything depends on domain, domain depends on nothing | |
| 16 | + | ||
| 17 | + | ## Dependency Flow | |
| 18 | + | ||
| 19 | + | ``` | |
| 20 | + | ┌─────────────────────────────────────┐ | |
| 21 | + | │ User Interface (TUI/CLI) │ ← Entry points | |
| 22 | + | ├─────────────────────────────────────┤ | |
| 23 | + | │ Application Layer │ ← Use cases (commands, handlers, services) | |
| 24 | + | ├─────────────────────────────────────┤ | |
| 25 | + | │ Domain Layer │ ← Business logic (entities, value objects, rules) | |
| 26 | + | ├─────────────────────────────────────┤ | |
| 27 | + | │ Infrastructure Layer │ ← Adapters implementing domain ports | |
| 28 | + | └─────────────────────────────────────┘ | |
| 29 | + | ``` | |
| 30 | + | ||
| 31 | + | Dependencies: UI → App → Domain ← Infrastructure | |
| 32 | + | ||
| 33 | + | ## Directory Structure | |
| 34 | + | ||
| 35 | + | ### `/internal/domain/` - Pure Business Logic | |
| 36 | + | ||
| 37 | + | **NO external dependencies allowed** (except: `context`, `errors`, `fmt`, `time`, `strings`, `github.com/google/uuid`) | |
| 38 | + | ||
| 39 | + | #### `domain/installation/` | |
| 40 | + | - **installation.go** - Installation aggregate root (entity) managing lifecycle | |
| 41 | + | - **state.go** - State enum and transition validation | |
| 42 | + | - **events.go** - Domain events (InstallationStarted, PhaseCompleted, etc.) | |
| 43 | + | - **errors.go** - Domain-specific error types | |
| 44 | + | ||
| 45 | + | #### `domain/system/` | |
| 46 | + | - **config.go** - SystemConfig value object (hostname, locale, timezone, keymap) | |
| 47 | + | - **validation.go** - System validation rules | |
| 48 | + | - **cpu.go** - CPU detection value object (vendor, microcode) | |
| 49 | + | ||
| 50 | + | #### `domain/disk/` | |
| 51 | + | - **disk.go** - Disk entity (device, size, partitions) | |
| 52 | + | - **partition.go** - Partition value object (device, size, filesystem, mount point) | |
| 53 | + | - **encryption.go** - Encryption value object (none/LUKS/LUKS-LVM, password requirements) | |
| 54 | + | - **layout.go** - Partition layout rules and validation | |
| 55 | + | ||
| 56 | + | #### `domain/bootloader/` | |
| 57 | + | - **bootloader.go** - Bootloader value object (type, configuration) | |
| 58 | + | - **limine.go** - Limine-specific configuration and rules | |
| 59 | + | - **systemdboot.go** - systemd-boot specific configuration | |
| 60 | + | ||
| 61 | + | #### `domain/user/` | |
| 62 | + | - **user.go** - User entity (username, groups, shell) | |
| 63 | + | - **credentials.go** - Credentials value object (password hashing, validation) | |
| 64 | + | - **validation.go** - User validation rules (username format, password strength) | |
| 65 | + | ||
| 66 | + | #### `domain/packages/` | |
| 67 | + | - **package.go** - Package value object | |
| 68 | + | - **repository.go** - Repository configuration (Arch Linux, AUR, Chaotic-AUR) | |
| 69 | + | - **kernel.go** - Kernel choice value object (linux, linux-zen, linux-lts, etc.) | |
| 70 | + | ||
| 71 | + | #### `domain/ports/` - Hexagonal Architecture Ports | |
| 72 | + | **ALL infrastructure interactions via these interfaces** | |
| 73 | + | ||
| 74 | + | - **filesystem.go** - FileSystem port (Read, Write, Chmod, MkdirAll, etc.) | |
| 75 | + | - **executor.go** - CommandExecutor, ChrootExecutor, ScriptExecutor ports | |
| 76 | + | - **repository.go** - InstallationRepository port (Save, Load) | |
| 77 | + | - **downloader.go** - HTTPClient port (Get, Post) | |
| 78 | + | - **logger.go** - Logger port (Info, Warn, Error, Debug) | |
| 79 | + | ||
| 80 | + | ### `/internal/application/` - Use Case Orchestration | |
| 81 | + | ||
| 82 | + | Glues together domain logic with infrastructure adapters. **Imports domain/** but not **infrastructure/**. | |
| 83 | + | ||
| 84 | + | #### `application/commands/` | |
| 85 | + | Plain data structures (DTOs) representing user intents: | |
| 86 | + | - **preflight.go** - PreflightCommand | |
| 87 | + | - **partition.go** - PartitionDiskCommand | |
| 88 | + | - **install_base.go** - InstallBaseCommand | |
| 89 | + | - **configure_system.go** - ConfigureSystemCommand | |
| 90 | + | - **install_bootloader.go** - InstallBootloaderCommand | |
| 91 | + | - **setup_repos.go** - SetupRepositoriesCommand | |
| 92 | + | - **post_install.go** - PostInstallCommand | |
| 93 | + | ||
| 94 | + | #### `application/handlers/` | |
| 95 | + | Command handlers implementing use cases. Each handler: | |
| 96 | + | 1. Receives a command (DTO) | |
| 97 | + | 2. Uses ports to gather system info | |
| 98 | + | 3. Creates domain objects with validation | |
| 99 | + | 4. Calls domain methods | |
| 100 | + | 5. Returns result (DTO) | |
| 101 | + | ||
| 102 | + | - **preflight_handler.go** - Handles PreflightCommand | |
| 103 | + | - **partition_handler.go** - Handles PartitionDiskCommand | |
| 104 | + | - **install_base_handler.go** - Handles InstallBaseCommand | |
| 105 | + | - **configure_system_handler.go** - Handles ConfigureSystemCommand | |
| 106 | + | - **bootloader_handler.go** - Handles InstallBootloaderCommand | |
| 107 | + | - **repos_handler.go** - Handles SetupRepositoriesCommand | |
| 108 | + | - **post_install_handler.go** - Handles PostInstallCommand | |
| 109 | + | ||
| 110 | + | #### `application/services/` | |
| 111 | + | Application services coordinating the overall installation: | |
| 112 | + | - **installation_service.go** - Main orchestrator managing all handlers and state | |
| 113 | + | - **progress_tracker.go** - Progress tracking with event publishing | |
| 114 | + | ||
| 115 | + | #### `application/dto/` | |
| 116 | + | Data Transfer Objects for communication across boundaries: | |
| 117 | + | - **installation_status.go** - InstallationStatus, PhaseStatus | |
| 118 | + | - **progress_update.go** - ProgressUpdate for UI subscribers | |
| 119 | + | ||
| 120 | + | ### `/internal/infrastructure/` - Adapters (External System Implementation) | |
| 121 | + | ||
| 122 | + | Implements domain ports. **Imports domain/** but NOT **application/**. | |
| 123 | + | ||
| 124 | + | #### `infrastructure/filesystem/` | |
| 125 | + | Filesystem port adapters: | |
| 126 | + | - **local.go** - LocalFileSystem (implements domain.FileSystem) | |
| 127 | + | - **mock.go** - MockFileSystem (for testing) | |
| 128 | + | ||
| 129 | + | #### `infrastructure/executor/` | |
| 130 | + | Command execution adapters: | |
| 131 | + | - **shell.go** - ShellExecutor (implements domain.CommandExecutor) | |
| 132 | + | - **chroot.go** - ChrootExecutor (implements domain.ChrootExecutor) | |
| 133 | + | - **script.go** - ScriptExecutor for mandatory shell scripts | |
| 134 | + | - **mock.go** - MockExecutor (for testing) | |
| 135 | + | ||
| 136 | + | #### `infrastructure/persistence/` | |
| 137 | + | Data persistence adapters: | |
| 138 | + | - **file_repository.go** - FileRepository (implements domain.InstallationRepository) | |
| 139 | + | - **config_file.go** - Config file loader/saver (shell-compatible format) | |
| 140 | + | ||
| 141 | + | #### `infrastructure/http/` | |
| 142 | + | HTTP client adapters: | |
| 143 | + | - **client.go** - HTTPClient (implements domain.Downloader) | |
| 144 | + | - **mock.go** - MockHTTPClient (for testing) | |
| 145 | + | ||
| 146 | + | #### `infrastructure/logger/` | |
| 147 | + | Logging adapters: | |
| 148 | + | - **slog_adapter.go** - SlogAdapter (implements domain.Logger, wraps Go's slog) | |
| 149 | + | ||
| 150 | + | ### `/internal/interfaces/` - Entry Points (Thin Layer) | |
| 151 | + | ||
| 152 | + | UI/CLI layer. **Imports application/** but minimal domain access. | |
| 153 | + | ||
| 154 | + | #### `interfaces/tui/` | |
| 155 | + | BubbleTea terminal UI application: | |
| 156 | + | - **app.go** - Main TUI application | |
| 157 | + | ||
| 158 | + | #### `interfaces/tui/models/` | |
| 159 | + | BubbleTea models (UI state only, no business logic): | |
| 160 | + | - **form.go** - Form input model | |
| 161 | + | - **installation.go** - Installation progress model | |
| 162 | + | - **progress.go** - Progress display model | |
| 163 | + | ||
| 164 | + | #### `interfaces/tui/views/` | |
| 165 | + | Pure presentation layer (rendering): | |
| 166 | + | - **form_view.go** - Form rendering | |
| 167 | + | - **progress_view.go** - Progress rendering | |
| 168 | + | - **summary_view.go** - Summary rendering | |
| 169 | + | ||
| 170 | + | #### `interfaces/tui/handlers/` | |
| 171 | + | UI event handlers (delegate to application services): | |
| 172 | + | - **form_handler.go** - Form submission handler | |
| 173 | + | - **install_handler.go** - Installation event handler | |
| 174 | + | ||
| 175 | + | #### `interfaces/cli/` (Future) | |
| 176 | + | CLI commands (e.g., using Cobra): | |
| 177 | + | - **root.go** - Root command | |
| 178 | + | - **install.go** - Install command | |
| 179 | + | ||
| 180 | + | ### `/cmd/archup-installer/` | |
| 181 | + | Application entry point with dependency injection: | |
| 182 | + | - **main.go** - Wire all dependencies (adapters, handlers, services, TUI) | |
| 183 | + | ||
| 184 | + | ## Architecture Patterns | |
| 185 | + | ||
| 186 | + | ### 1. Aggregate Root (Installation) | |
| 187 | + | ||
| 188 | + | The `Installation` aggregate manages the entire installation lifecycle: | |
| 189 | + | ``` | |
| 190 | + | Installation (aggregate root) | |
| 191 | + | ├── state: InstallationState | |
| 192 | + | ├── config: InstallationConfig (value object) | |
| 193 | + | ├── system: SystemConfig (value object) | |
| 194 | + | ├── disk: Disk (entity with partitions) | |
| 195 | + | ├── user: User (entity) | |
| 196 | + | └── ... | |
| 197 | + | ``` | |
| 198 | + | ||
| 199 | + | ### 2. Value Objects | |
| 200 | + | ||
| 201 | + | Immutable, validated objects representing concepts: | |
| 202 | + | - `SystemConfig` - hostname, timezone, locale | |
| 203 | + | - `Encryption` - encryption type and password requirements | |
| 204 | + | - `Credentials` - user password (hashed) | |
| 205 | + | - `Kernel` - kernel choice | |
| 206 | + | ||
| 207 | + | ### 3. Ports (Interfaces) | |
| 208 | + | ||
| 209 | + | Domain defines what external systems must provide: | |
| 210 | + | ```go | |
| 211 | + | // domain/ports/filesystem.go | |
| 212 | + | type FileSystem interface { | |
| 213 | + | ReadFile(path string) ([]byte, error) | |
| 214 | + | WriteFile(path string, data []byte, perm os.FileMode) error | |
| 215 | + | Chmod(path string, perm os.FileMode) error | |
| 216 | + | MkdirAll(path string, perm os.FileMode) error | |
| 217 | + | } | |
| 218 | + | ``` | |
| 219 | + | ||
| 220 | + | ### 4. Adapters | |
| 221 | + | ||
| 222 | + | Infrastructure implements ports: | |
| 223 | + | ```go | |
| 224 | + | // infrastructure/filesystem/local.go | |
| 225 | + | type LocalFileSystem struct{} | |
| 226 | + | ||
| 227 | + | func (fs *LocalFileSystem) ReadFile(path string) ([]byte, error) { | |
| 228 | + | return os.ReadFile(path) | |
| 229 | + | } | |
| 230 | + | // ... implements domain.FileSystem | |
| 231 | + | ``` | |
| 232 | + | ||
| 233 | + | ### 5. Command Handler Pattern | |
| 234 | + | ||
| 235 | + | Each use case has: | |
| 236 | + | 1. **Command** (DTO): Input data | |
| 237 | + | 2. **Handler**: Orchestrates domain + infrastructure | |
| 238 | + | 3. **Result** (DTO): Output data | |
| 239 | + | ||
| 240 | + | ```go | |
| 241 | + | // Define command | |
| 242 | + | cmd := commands.PreflightCommand{ | |
| 243 | + | MountPoint: "/mnt", | |
| 244 | + | } | |
| 245 | + | ||
| 246 | + | // Execute via handler | |
| 247 | + | result, err := handler.Handle(ctx, cmd) | |
| 248 | + | ``` | |
| 249 | + | ||
| 250 | + | ## Testing Strategy | |
| 251 | + | ||
| 252 | + | ### Domain Tests | |
| 253 | + | - **Where**: `internal/domain/*/` with `_test.go` files | |
| 254 | + | - **What**: Pure unit tests of business rules | |
| 255 | + | - **How**: No mocks, no external dependencies | |
| 256 | + | - **Speed**: Fast (milliseconds) | |
| 257 | + | ||
| 258 | + | ```go | |
| 259 | + | func TestSystemConfig_Validation(t *testing.T) { | |
| 260 | + | cfg, err := domain.NewSystemConfig("hostname", "UTC", "en_US.UTF-8", "us") | |
| 261 | + | assert.NoError(t, err) | |
| 262 | + | assert.Equal(t, "hostname", cfg.Hostname()) | |
| 263 | + | } | |
| 264 | + | ``` | |
| 265 | + | ||
| 266 | + | ### Application Tests | |
| 267 | + | - **Where**: `internal/application/*/` with `_test.go` files | |
| 268 | + | - **What**: Handler logic with mocked ports | |
| 269 | + | - **How**: Use mock implementations (infrastructure/*/mock.go) | |
| 270 | + | - **Speed**: Fast (milliseconds) | |
| 271 | + | ||
| 272 | + | ```go | |
| 273 | + | func TestPreflightHandler_Handle(t *testing.T) { | |
| 274 | + | mockFS := &filesystem.MockFileSystem{} | |
| 275 | + | mockExec := &executor.MockExecutor{} | |
| 276 | + | handler := handlers.NewPreflightHandler(mockFS, mockExec, logger) | |
| 277 | + | ||
| 278 | + | result, err := handler.Handle(ctx, cmd) | |
| 279 | + | assert.NoError(t, err) | |
| 280 | + | } | |
| 281 | + | ``` | |
| 282 | + | ||
| 283 | + | ### Integration Tests | |
| 284 | + | - **Where**: `test/integration/` or similar | |
| 285 | + | - **What**: Full end-to-end scenarios | |
| 286 | + | - **How**: Real adapters in VM environment | |
| 287 | + | - **Speed**: Slow (minutes), run before commit | |
| 288 | + | ||
| 289 | + | ```go | |
| 290 | + | func TestFullInstallation(t *testing.T) { | |
| 291 | + | // End-to-end test in VM | |
| 292 | + | // Uses real filesystem, chroot, pacstrap, etc. | |
| 293 | + | } | |
| 294 | + | ``` | |
| 295 | + | ||
| 296 | + | ## Migration Checklist | |
| 297 | + | ||
| 298 | + | For each phase migrated from shell scripts: | |
| 299 | + | ||
| 300 | + | 1. **Extract business rules** → domain layer | |
| 301 | + | 2. **Create value objects** with validation | |
| 302 | + | 3. **Define port interfaces** needed | |
| 303 | + | 4. **Create command DTO** for input | |
| 304 | + | 5. **Implement handler** in application layer | |
| 305 | + | 6. **Create adapters** implementing ports | |
| 306 | + | 7. **Write domain tests** (pure unit tests) | |
| 307 | + | 8. **Write application tests** (with mocks) | |
| 308 | + | 9. **Write integration tests** (in VM) | |
| 309 | + | 10. **Update TUI** to use handler | |
| 310 | + | 11. **Test in VM** to verify functionality | |
| 311 | + | 12. **Document changes** | |
| 312 | + | ||
| 313 | + | ## Development Workflow | |
| 314 | + | ||
| 315 | + | ### Adding a New Feature | |
| 316 | + | ||
| 317 | + | 1. Define domain rules in `domain/*/` | |
| 318 | + | 2. Create command in `application/commands/` | |
| 319 | + | 3. Create handler in `application/handlers/` | |
| 320 | + | 4. Implement adapters in `infrastructure/*/` | |
| 321 | + | 5. Add UI in `interfaces/tui/` | |
| 322 | + | 6. Wire in `cmd/archup-installer/main.go` | |
| 323 | + | ||
| 324 | + | ### Fixing a Bug | |
| 325 | + | ||
| 326 | + | 1. Write failing test at appropriate layer | |
| 327 | + | 2. Fix in domain (if business logic) or adapter (if infrastructure) | |
| 328 | + | 3. Verify tests pass | |
| 329 | + | 4. Test in VM if infrastructure-related | |
| 330 | + | ||
| 331 | + | ### Changing Infrastructure | |
| 332 | + | ||
| 333 | + | 1. Create new adapter implementing port | |
| 334 | + | 2. Update `cmd/archup-installer/main.go` wiring | |
| 335 | + | 3. Existing domain and application code unchanged | |
| 336 | + | 4. No need to modify business logic | |
| 337 | + | ||
| 338 | + | ## Important Files | |
| 339 | + | ||
| 340 | + | - **DDD_REFACTORING_PLAN.md** - Complete refactoring plan with detailed phases | |
| 341 | + | - **docs/script-migration.md** - Shell scripts categorization and migration strategy | |
| 342 | + | - **internal/domain/ports/** - All port interfaces (critical!) | |
| 343 | + | - **cmd/archup-installer/main.go** - Dependency injection wiring | |
| 344 | + | ||
| 345 | + | ## References | |
| 346 | + | ||
| 347 | + | - Eric Evans - Domain-Driven Design (DDD book) | |
| 348 | + | - Hexagonal Architecture (Ports & Adapters) | |
| 349 | + | - Clean Architecture by Robert C. Martin | |
| 350 | + | - Project DDD Plan: `DDD_REFACTORING_PLAN.md` | |
| 351 | + | - Shell Scripts Audit: `docs/script-migration.md` | |
| 352 | + | ||
| 353 | + | ## Key Rules | |
| 354 | + | ||
| 355 | + | ### Domain Layer | |
| 356 | + | - ✅ Pure business logic | |
| 357 | + | - ✅ Validation of invariants | |
| 358 | + | - ✅ Value objects (immutable) | |
| 359 | + | - ✅ Entities with identity | |
| 360 | + | - ❌ NO external dependencies | |
| 361 | + | - ❌ NO file system access | |
| 362 | + | - ❌ NO HTTP calls | |
| 363 | + | - ❌ NO database access | |
| 364 | + | ||
| 365 | + | ### Application Layer | |
| 366 | + | - ✅ Orchestrates domain + infrastructure | |
| 367 | + | - ✅ Implements use cases | |
| 368 | + | - ✅ Delegates to ports | |
| 369 | + | - ✅ Returns DTOs | |
| 370 | + | - ❌ NO business logic | |
| 371 | + | - ❌ NO direct infrastructure calls | |
| 372 | + | - ❌ NO direct UI logic | |
| 373 | + | ||
| 374 | + | ### Infrastructure Layer | |
| 375 | + | - ✅ Implements ports | |
| 376 | + | - ✅ Handles external systems | |
| 377 | + | - ✅ Contains adapters | |
| 378 | + | - ❌ NO business logic | |
| 379 | + | - ❌ NO use case orchestration | |
| 380 | + | ||
| 381 | + | ### Interfaces Layer | |
| 382 | + | - ✅ Handles user input/output | |
| 383 | + | - ✅ Delegates to application services | |
| 384 | + | - ✅ Pure presentation | |
| 385 | + | - ❌ NO business logic | |
| 386 | + | - ❌ NO infrastructure details | |
| 387 | + | ||
| 388 | + | --- | |
| 389 | + | ||
| 390 | + | **Status**: Phase 2 Complete - Directory structure established | |
| 391 | + | **Next**: Phase 3 - Define Domain Layer with entities and value objects | |
| @@ -0,0 +1,391 @@ | |||||
| 1 | + | # ArchUp Architecture - Domain-Driven Design (DDD) | |||
| 2 | + | ||||
| 3 | + | ## Overview | |||
| 4 | + | ||||
| 5 | + | ArchUp has been restructured using Domain-Driven Design (DDD) principles to achieve: | |||
| 6 | + | - Better testability (domain logic tested without infrastructure) | |||
| 7 | + | - Clear separation of concerns (business logic isolated from infrastructure) | |||
| 8 | + | - Improved maintainability (easy to extend without modifying existing code) | |||
| 9 | + | - Reduced coupling (dependencies point inward) | |||
| 10 | + | ||||
| 11 | + | ## Core Principles | |||
| 12 | + | ||||
| 13 | + | 1. **Business logic ONLY in domain** - NO external dependencies (no OS, HTTP, DB, etc.) | |||
| 14 | + | 2. **Domain defines interfaces (Ports)** - Infrastructure implements them (Adapters) | |||
| 15 | + | 3. **Dependencies point INWARD** - Everything depends on domain, domain depends on nothing | |||
| 16 | + | ||||
| 17 | + | ## Dependency Flow | |||
| 18 | + | ||||
| 19 | + | ``` | |||
| 20 | + | ┌─────────────────────────────────────┐ | |||
| 21 | + | │ User Interface (TUI/CLI) │ ← Entry points | |||
| 22 | + | ├─────────────────────────────────────┤ | |||
| 23 | + | │ Application Layer │ ← Use cases (commands, handlers, services) | |||
| 24 | + | ├─────────────────────────────────────┤ | |||
| 25 | + | │ Domain Layer │ ← Business logic (entities, value objects, rules) | |||
| 26 | + | ├─────────────────────────────────────┤ | |||
| 27 | + | │ Infrastructure Layer │ ← Adapters implementing domain ports | |||
| 28 | + | └─────────────────────────────────────┘ | |||
| 29 | + | ``` | |||
| 30 | + | ||||
| 31 | + | Dependencies: UI → App → Domain ← Infrastructure | |||
| 32 | + | ||||
| 33 | + | ## Directory Structure | |||
| 34 | + | ||||
| 35 | + | ### `/internal/domain/` - Pure Business Logic | |||
| 36 | + | ||||
| 37 | + | **NO external dependencies allowed** (except: `context`, `errors`, `fmt`, `time`, `strings`, `github.com/google/uuid`) | |||
| 38 | + | ||||
| 39 | + | #### `domain/installation/` | |||
| 40 | + | - **installation.go** - Installation aggregate root (entity) managing lifecycle | |||
| 41 | + | - **state.go** - State enum and transition validation | |||
| 42 | + | - **events.go** - Domain events (InstallationStarted, PhaseCompleted, etc.) | |||
| 43 | + | - **errors.go** - Domain-specific error types | |||
| 44 | + | ||||
| 45 | + | #### `domain/system/` | |||
| 46 | + | - **config.go** - SystemConfig value object (hostname, locale, timezone, keymap) | |||
| 47 | + | - **validation.go** - System validation rules | |||
| 48 | + | - **cpu.go** - CPU detection value object (vendor, microcode) | |||
| 49 | + | ||||
| 50 | + | #### `domain/disk/` | |||
| 51 | + | - **disk.go** - Disk entity (device, size, partitions) | |||
| 52 | + | - **partition.go** - Partition value object (device, size, filesystem, mount point) | |||
| 53 | + | - **encryption.go** - Encryption value object (none/LUKS/LUKS-LVM, password requirements) | |||
| 54 | + | - **layout.go** - Partition layout rules and validation | |||
| 55 | + | ||||
| 56 | + | #### `domain/bootloader/` | |||
| 57 | + | - **bootloader.go** - Bootloader value object (type, configuration) | |||
| 58 | + | - **limine.go** - Limine-specific configuration and rules | |||
| 59 | + | - **systemdboot.go** - systemd-boot specific configuration | |||
| 60 | + | ||||
| 61 | + | #### `domain/user/` | |||
| 62 | + | - **user.go** - User entity (username, groups, shell) | |||
| 63 | + | - **credentials.go** - Credentials value object (password hashing, validation) | |||
| 64 | + | - **validation.go** - User validation rules (username format, password strength) | |||
| 65 | + | ||||
| 66 | + | #### `domain/packages/` | |||
| 67 | + | - **package.go** - Package value object | |||
| 68 | + | - **repository.go** - Repository configuration (Arch Linux, AUR, Chaotic-AUR) | |||
| 69 | + | - **kernel.go** - Kernel choice value object (linux, linux-zen, linux-lts, etc.) | |||
| 70 | + | ||||
| 71 | + | #### `domain/ports/` - Hexagonal Architecture Ports | |||
| 72 | + | **ALL infrastructure interactions via these interfaces** | |||
| 73 | + | ||||
| 74 | + | - **filesystem.go** - FileSystem port (Read, Write, Chmod, MkdirAll, etc.) | |||
| 75 | + | - **executor.go** - CommandExecutor, ChrootExecutor, ScriptExecutor ports | |||
| 76 | + | - **repository.go** - InstallationRepository port (Save, Load) | |||
| 77 | + | - **downloader.go** - HTTPClient port (Get, Post) | |||
| 78 | + | - **logger.go** - Logger port (Info, Warn, Error, Debug) | |||
| 79 | + | ||||
| 80 | + | ### `/internal/application/` - Use Case Orchestration | |||
| 81 | + | ||||
| 82 | + | Glues together domain logic with infrastructure adapters. **Imports domain/** but not **infrastructure/**. | |||
| 83 | + | ||||
| 84 | + | #### `application/commands/` | |||
| 85 | + | Plain data structures (DTOs) representing user intents: | |||
| 86 | + | - **preflight.go** - PreflightCommand | |||
| 87 | + | - **partition.go** - PartitionDiskCommand | |||
| 88 | + | - **install_base.go** - InstallBaseCommand | |||
| 89 | + | - **configure_system.go** - ConfigureSystemCommand | |||
| 90 | + | - **install_bootloader.go** - InstallBootloaderCommand | |||
| 91 | + | - **setup_repos.go** - SetupRepositoriesCommand | |||
| 92 | + | - **post_install.go** - PostInstallCommand | |||
| 93 | + | ||||
| 94 | + | #### `application/handlers/` | |||
| 95 | + | Command handlers implementing use cases. Each handler: | |||
| 96 | + | 1. Receives a command (DTO) | |||
| 97 | + | 2. Uses ports to gather system info | |||
| 98 | + | 3. Creates domain objects with validation | |||
| 99 | + | 4. Calls domain methods | |||
| 100 | + | 5. Returns result (DTO) | |||
| 101 | + | ||||
| 102 | + | - **preflight_handler.go** - Handles PreflightCommand | |||
| 103 | + | - **partition_handler.go** - Handles PartitionDiskCommand | |||
| 104 | + | - **install_base_handler.go** - Handles InstallBaseCommand | |||
| 105 | + | - **configure_system_handler.go** - Handles ConfigureSystemCommand | |||
| 106 | + | - **bootloader_handler.go** - Handles InstallBootloaderCommand | |||
| 107 | + | - **repos_handler.go** - Handles SetupRepositoriesCommand | |||
| 108 | + | - **post_install_handler.go** - Handles PostInstallCommand | |||
| 109 | + | ||||
| 110 | + | #### `application/services/` | |||
| 111 | + | Application services coordinating the overall installation: | |||
| 112 | + | - **installation_service.go** - Main orchestrator managing all handlers and state | |||
| 113 | + | - **progress_tracker.go** - Progress tracking with event publishing | |||
| 114 | + | ||||
| 115 | + | #### `application/dto/` | |||
| 116 | + | Data Transfer Objects for communication across boundaries: | |||
| 117 | + | - **installation_status.go** - InstallationStatus, PhaseStatus | |||
| 118 | + | - **progress_update.go** - ProgressUpdate for UI subscribers | |||
| 119 | + | ||||
| 120 | + | ### `/internal/infrastructure/` - Adapters (External System Implementation) | |||
| 121 | + | ||||
| 122 | + | Implements domain ports. **Imports domain/** but NOT **application/**. | |||
| 123 | + | ||||
| 124 | + | #### `infrastructure/filesystem/` | |||
| 125 | + | Filesystem port adapters: | |||
| 126 | + | - **local.go** - LocalFileSystem (implements domain.FileSystem) | |||
| 127 | + | - **mock.go** - MockFileSystem (for testing) | |||
| 128 | + | ||||
| 129 | + | #### `infrastructure/executor/` | |||
| 130 | + | Command execution adapters: | |||
| 131 | + | - **shell.go** - ShellExecutor (implements domain.CommandExecutor) | |||
| 132 | + | - **chroot.go** - ChrootExecutor (implements domain.ChrootExecutor) | |||
| 133 | + | - **script.go** - ScriptExecutor for mandatory shell scripts | |||
| 134 | + | - **mock.go** - MockExecutor (for testing) | |||
| 135 | + | ||||
| 136 | + | #### `infrastructure/persistence/` | |||
| 137 | + | Data persistence adapters: | |||
| 138 | + | - **file_repository.go** - FileRepository (implements domain.InstallationRepository) | |||
| 139 | + | - **config_file.go** - Config file loader/saver (shell-compatible format) | |||
| 140 | + | ||||
| 141 | + | #### `infrastructure/http/` | |||
| 142 | + | HTTP client adapters: | |||
| 143 | + | - **client.go** - HTTPClient (implements domain.Downloader) | |||
| 144 | + | - **mock.go** - MockHTTPClient (for testing) | |||
| 145 | + | ||||
| 146 | + | #### `infrastructure/logger/` | |||
| 147 | + | Logging adapters: | |||
| 148 | + | - **slog_adapter.go** - SlogAdapter (implements domain.Logger, wraps Go's slog) | |||
| 149 | + | ||||
| 150 | + | ### `/internal/interfaces/` - Entry Points (Thin Layer) | |||
| 151 | + | ||||
| 152 | + | UI/CLI layer. **Imports application/** but minimal domain access. | |||
| 153 | + | ||||
| 154 | + | #### `interfaces/tui/` | |||
| 155 | + | BubbleTea terminal UI application: | |||
| 156 | + | - **app.go** - Main TUI application | |||
| 157 | + | ||||
| 158 | + | #### `interfaces/tui/models/` | |||
| 159 | + | BubbleTea models (UI state only, no business logic): | |||
| 160 | + | - **form.go** - Form input model | |||
| 161 | + | - **installation.go** - Installation progress model | |||
| 162 | + | - **progress.go** - Progress display model | |||
| 163 | + | ||||
| 164 | + | #### `interfaces/tui/views/` | |||
| 165 | + | Pure presentation layer (rendering): | |||
| 166 | + | - **form_view.go** - Form rendering | |||
| 167 | + | - **progress_view.go** - Progress rendering | |||
| 168 | + | - **summary_view.go** - Summary rendering | |||
| 169 | + | ||||
| 170 | + | #### `interfaces/tui/handlers/` | |||
| 171 | + | UI event handlers (delegate to application services): | |||
| 172 | + | - **form_handler.go** - Form submission handler | |||
| 173 | + | - **install_handler.go** - Installation event handler | |||
| 174 | + | ||||
| 175 | + | #### `interfaces/cli/` (Future) | |||
| 176 | + | CLI commands (e.g., using Cobra): | |||
| 177 | + | - **root.go** - Root command | |||
| 178 | + | - **install.go** - Install command | |||
| 179 | + | ||||
| 180 | + | ### `/cmd/archup-installer/` | |||
| 181 | + | Application entry point with dependency injection: | |||
| 182 | + | - **main.go** - Wire all dependencies (adapters, handlers, services, TUI) | |||
| 183 | + | ||||
| 184 | + | ## Architecture Patterns | |||
| 185 | + | ||||
| 186 | + | ### 1. Aggregate Root (Installation) | |||
| 187 | + | ||||
| 188 | + | The `Installation` aggregate manages the entire installation lifecycle: | |||
| 189 | + | ``` | |||
| 190 | + | Installation (aggregate root) | |||
| 191 | + | ├── state: InstallationState | |||
| 192 | + | ├── config: InstallationConfig (value object) | |||
| 193 | + | ├── system: SystemConfig (value object) | |||
| 194 | + | ├── disk: Disk (entity with partitions) | |||
| 195 | + | ├── user: User (entity) | |||
| 196 | + | └── ... | |||
| 197 | + | ``` | |||
| 198 | + | ||||
| 199 | + | ### 2. Value Objects | |||
| 200 | + | ||||
| 201 | + | Immutable, validated objects representing concepts: | |||
| 202 | + | - `SystemConfig` - hostname, timezone, locale | |||
| 203 | + | - `Encryption` - encryption type and password requirements | |||
| 204 | + | - `Credentials` - user password (hashed) | |||
| 205 | + | - `Kernel` - kernel choice | |||
| 206 | + | ||||
| 207 | + | ### 3. Ports (Interfaces) | |||
| 208 | + | ||||
| 209 | + | Domain defines what external systems must provide: | |||
| 210 | + | ```go | |||
| 211 | + | // domain/ports/filesystem.go | |||
| 212 | + | type FileSystem interface { | |||
| 213 | + | ReadFile(path string) ([]byte, error) | |||
| 214 | + | WriteFile(path string, data []byte, perm os.FileMode) error | |||
| 215 | + | Chmod(path string, perm os.FileMode) error | |||
| 216 | + | MkdirAll(path string, perm os.FileMode) error | |||
| 217 | + | } | |||
| 218 | + | ``` | |||
| 219 | + | ||||
| 220 | + | ### 4. Adapters | |||
| 221 | + | ||||
| 222 | + | Infrastructure implements ports: | |||
| 223 | + | ```go | |||
| 224 | + | // infrastructure/filesystem/local.go | |||
| 225 | + | type LocalFileSystem struct{} | |||
| 226 | + | ||||
| 227 | + | func (fs *LocalFileSystem) ReadFile(path string) ([]byte, error) { | |||
| 228 | + | return os.ReadFile(path) | |||
| 229 | + | } | |||
| 230 | + | // ... implements domain.FileSystem | |||
| 231 | + | ``` | |||
| 232 | + | ||||
| 233 | + | ### 5. Command Handler Pattern | |||
| 234 | + | ||||
| 235 | + | Each use case has: | |||
| 236 | + | 1. **Command** (DTO): Input data | |||
| 237 | + | 2. **Handler**: Orchestrates domain + infrastructure | |||
| 238 | + | 3. **Result** (DTO): Output data | |||
| 239 | + | ||||
| 240 | + | ```go | |||
| 241 | + | // Define command | |||
| 242 | + | cmd := commands.PreflightCommand{ | |||
| 243 | + | MountPoint: "/mnt", | |||
| 244 | + | } | |||
| 245 | + | ||||
| 246 | + | // Execute via handler | |||
| 247 | + | result, err := handler.Handle(ctx, cmd) | |||
| 248 | + | ``` | |||
| 249 | + | ||||
| 250 | + | ## Testing Strategy | |||
| 251 | + | ||||
| 252 | + | ### Domain Tests | |||
| 253 | + | - **Where**: `internal/domain/*/` with `_test.go` files | |||
| 254 | + | - **What**: Pure unit tests of business rules | |||
| 255 | + | - **How**: No mocks, no external dependencies | |||
| 256 | + | - **Speed**: Fast (milliseconds) | |||
| 257 | + | ||||
| 258 | + | ```go | |||
| 259 | + | func TestSystemConfig_Validation(t *testing.T) { | |||
| 260 | + | cfg, err := domain.NewSystemConfig("hostname", "UTC", "en_US.UTF-8", "us") | |||
| 261 | + | assert.NoError(t, err) | |||
| 262 | + | assert.Equal(t, "hostname", cfg.Hostname()) | |||
| 263 | + | } | |||
| 264 | + | ``` | |||
| 265 | + | ||||
| 266 | + | ### Application Tests | |||
| 267 | + | - **Where**: `internal/application/*/` with `_test.go` files | |||
| 268 | + | - **What**: Handler logic with mocked ports | |||
| 269 | + | - **How**: Use mock implementations (infrastructure/*/mock.go) | |||
| 270 | + | - **Speed**: Fast (milliseconds) | |||
| 271 | + | ||||
| 272 | + | ```go | |||
| 273 | + | func TestPreflightHandler_Handle(t *testing.T) { | |||
| 274 | + | mockFS := &filesystem.MockFileSystem{} | |||
| 275 | + | mockExec := &executor.MockExecutor{} | |||
| 276 | + | handler := handlers.NewPreflightHandler(mockFS, mockExec, logger) | |||
| 277 | + | ||||
| 278 | + | result, err := handler.Handle(ctx, cmd) | |||
| 279 | + | assert.NoError(t, err) | |||
| 280 | + | } | |||
| 281 | + | ``` | |||
| 282 | + | ||||
| 283 | + | ### Integration Tests | |||
| 284 | + | - **Where**: `test/integration/` or similar | |||
| 285 | + | - **What**: Full end-to-end scenarios | |||
| 286 | + | - **How**: Real adapters in VM environment | |||
| 287 | + | - **Speed**: Slow (minutes), run before commit | |||
| 288 | + | ||||
| 289 | + | ```go | |||
| 290 | + | func TestFullInstallation(t *testing.T) { | |||
| 291 | + | // End-to-end test in VM | |||
| 292 | + | // Uses real filesystem, chroot, pacstrap, etc. | |||
| 293 | + | } | |||
| 294 | + | ``` | |||
| 295 | + | ||||
| 296 | + | ## Migration Checklist | |||
| 297 | + | ||||
| 298 | + | For each phase migrated from shell scripts: | |||
| 299 | + | ||||
| 300 | + | 1. **Extract business rules** → domain layer | |||
| 301 | + | 2. **Create value objects** with validation | |||
| 302 | + | 3. **Define port interfaces** needed | |||
| 303 | + | 4. **Create command DTO** for input | |||
| 304 | + | 5. **Implement handler** in application layer | |||
| 305 | + | 6. **Create adapters** implementing ports | |||
| 306 | + | 7. **Write domain tests** (pure unit tests) | |||
| 307 | + | 8. **Write application tests** (with mocks) | |||
| 308 | + | 9. **Write integration tests** (in VM) | |||
| 309 | + | 10. **Update TUI** to use handler | |||
| 310 | + | 11. **Test in VM** to verify functionality | |||
| 311 | + | 12. **Document changes** | |||
| 312 | + | ||||
| 313 | + | ## Development Workflow | |||
| 314 | + | ||||
| 315 | + | ### Adding a New Feature | |||
| 316 | + | ||||
| 317 | + | 1. Define domain rules in `domain/*/` | |||
| 318 | + | 2. Create command in `application/commands/` | |||
| 319 | + | 3. Create handler in `application/handlers/` | |||
| 320 | + | 4. Implement adapters in `infrastructure/*/` | |||
| 321 | + | 5. Add UI in `interfaces/tui/` | |||
| 322 | + | 6. Wire in `cmd/archup-installer/main.go` | |||
| 323 | + | ||||
| 324 | + | ### Fixing a Bug | |||
| 325 | + | ||||
| 326 | + | 1. Write failing test at appropriate layer | |||
| 327 | + | 2. Fix in domain (if business logic) or adapter (if infrastructure) | |||
| 328 | + | 3. Verify tests pass | |||
| 329 | + | 4. Test in VM if infrastructure-related | |||
| 330 | + | ||||
| 331 | + | ### Changing Infrastructure | |||
| 332 | + | ||||
| 333 | + | 1. Create new adapter implementing port | |||
| 334 | + | 2. Update `cmd/archup-installer/main.go` wiring | |||
| 335 | + | 3. Existing domain and application code unchanged | |||
| 336 | + | 4. No need to modify business logic | |||
| 337 | + | ||||
| 338 | + | ## Important Files | |||
| 339 | + | ||||
| 340 | + | - **DDD_REFACTORING_PLAN.md** - Complete refactoring plan with detailed phases | |||
| 341 | + | - **docs/script-migration.md** - Shell scripts categorization and migration strategy | |||
| 342 | + | - **internal/domain/ports/** - All port interfaces (critical!) | |||
| 343 | + | - **cmd/archup-installer/main.go** - Dependency injection wiring | |||
| 344 | + | ||||
| 345 | + | ## References | |||
| 346 | + | ||||
| 347 | + | - Eric Evans - Domain-Driven Design (DDD book) | |||
| 348 | + | - Hexagonal Architecture (Ports & Adapters) | |||
| 349 | + | - Clean Architecture by Robert C. Martin | |||
| 350 | + | - Project DDD Plan: `DDD_REFACTORING_PLAN.md` | |||
| 351 | + | - Shell Scripts Audit: `docs/script-migration.md` | |||
| 352 | + | ||||
| 353 | + | ## Key Rules | |||
| 354 | + | ||||
| 355 | + | ### Domain Layer | |||
| 356 | + | - ✅ Pure business logic | |||
| 357 | + | - ✅ Validation of invariants | |||
| 358 | + | - ✅ Value objects (immutable) | |||
| 359 | + | - ✅ Entities with identity | |||
| 360 | + | - ❌ NO external dependencies | |||
| 361 | + | - ❌ NO file system access | |||
| 362 | + | - ❌ NO HTTP calls | |||
| 363 | + | - ❌ NO database access | |||
| 364 | + | ||||
| 365 | + | ### Application Layer | |||
| 366 | + | - ✅ Orchestrates domain + infrastructure | |||
| 367 | + | - ✅ Implements use cases | |||
| 368 | + | - ✅ Delegates to ports | |||
| 369 | + | - ✅ Returns DTOs | |||
| 370 | + | - ❌ NO business logic | |||
| 371 | + | - ❌ NO direct infrastructure calls | |||
| 372 | + | - ❌ NO direct UI logic | |||
| 373 | + | ||||
| 374 | + | ### Infrastructure Layer | |||
| 375 | + | - ✅ Implements ports | |||
| 376 | + | - ✅ Handles external systems | |||
| 377 | + | - ✅ Contains adapters | |||
| 378 | + | - ❌ NO business logic | |||
| 379 | + | - ❌ NO use case orchestration | |||
| 380 | + | ||||
| 381 | + | ### Interfaces Layer | |||
| 382 | + | - ✅ Handles user input/output | |||
| 383 | + | - ✅ Delegates to application services | |||
| 384 | + | - ✅ Pure presentation | |||
| 385 | + | - ❌ NO business logic | |||
| 386 | + | - ❌ NO infrastructure details | |||
| 387 | + | ||||
| 388 | + | --- | |||
| 389 | + | ||||
| 390 | + | **Status**: Phase 2 Complete - Directory structure established | |||
| 391 | + | **Next**: Phase 3 - Define Domain Layer with entities and value objects | |||
A internal/application/.gitkeep +0 -0
no textual change.
A internal/application/commands/.gitkeep +0 -0
no textual change.
A internal/application/dto/.gitkeep +0 -0
no textual change.
A internal/application/handlers/.gitkeep +0 -0
no textual change.
A internal/application/services/.gitkeep +0 -0
no textual change.
A internal/domain/.gitkeep +0 -0
no textual change.
A internal/domain/bootloader/.gitkeep +0 -0
no textual change.
A internal/domain/disk/.gitkeep +0 -0
no textual change.
A internal/domain/installation/.gitkeep +0 -0
no textual change.
A internal/domain/packages/.gitkeep +0 -0
no textual change.
A internal/domain/ports/.gitkeep +0 -0
no textual change.
A internal/domain/system/.gitkeep +0 -0
no textual change.
A internal/domain/user/.gitkeep +0 -0
no textual change.
A internal/infrastructure/.gitkeep +0 -0
no textual change.
A internal/infrastructure/executor/.gitkeep +0 -0
no textual change.
A internal/infrastructure/filesystem/.gitkeep +0 -0
no textual change.
A internal/infrastructure/http/.gitkeep +0 -0
no textual change.
A internal/infrastructure/logger/.gitkeep +0 -0
no textual change.
A internal/infrastructure/persistence/.gitkeep +0 -0
no textual change.
A internal/interfaces/.gitkeep +0 -0
no textual change.
A internal/interfaces/cli/.gitkeep +0 -0
no textual change.
A internal/interfaces/mocks/.gitkeep +0 -0
no textual change.
A internal/interfaces/tui/.gitkeep +0 -0
no textual change.
A internal/interfaces/tui/handlers/.gitkeep +0 -0
no textual change.
A internal/interfaces/tui/models/.gitkeep +0 -0
no textual change.
A internal/interfaces/tui/views/.gitkeep +0 -0
no textual change.