Files
compose-farm/docs/dev/future-improvements.md
Bas Nijholt 566a07d3a4 Refactor: separate concerns into dedicated modules
- Extract compose.py from traefik.py for generic compose parsing
  (env loading, interpolation, ports, volumes, networks)
- Rename ssh.py to executor.py for clarity
- Extract operations.py from cli.py for business logic
  (up_services, discover_running_services, preflight checks)
- Update CLAUDE.md with new architecture diagram
- Add docs/dev/future-improvements.md for low-priority items

CLI is now a thin layer that delegates to operations module.
All 70 tests pass.
2025-12-14 12:49:24 -08:00

3.8 KiB

Future Improvements

Low-priority improvements identified during code review. These are not currently causing issues but could be addressed if they become pain points.

1. State Module Efficiency (LOW)

Current: Every state operation reads and writes the entire file.

def set_service_host(config, service, host):
    state = load_state(config)   # Read file
    state[service] = host
    save_state(config, state)    # Write file

Impact: With 87 services, this is fine. With 1000+, it would be slow.

Potential fix: Add batch operations:

def update_state(config, updates: dict[str, str | None]) -> None:
    """Batch update: set services to hosts, None means remove."""
    state = load_state(config)
    for service, host in updates.items():
        if host is None:
            state.pop(service, None)
        else:
            state[service] = host
    save_state(config, state)

When to do: Only if state operations become noticeably slow.


2. Remote-Aware Compose Path Resolution (LOW)

Current: config.get_compose_path() checks if files exist on the local filesystem:

def get_compose_path(self, service: str) -> Path:
    for filename in ("compose.yaml", "compose.yml", ...):
        candidate = service_dir / filename
        if candidate.exists():  # Local check!
            return candidate

Why this works: NFS/shared storage means local = remote.

Why it could break: If running compose-farm from a machine without the NFS mount, it returns compose.yaml (the default) even if docker-compose.yml exists on the remote host.

Potential fix: Query the remote host for file existence, or accept this limitation and document it.

When to do: Only if users need to run compose-farm from non-NFS machines.


3. Add Integration Tests for CLI Commands (MEDIUM)

Current: No integration tests for the actual CLI commands. Tests cover the underlying functions but not the Typer commands themselves.

Potential fix: Add integration tests using CliRunner from Typer:

from typer.testing import CliRunner
from compose_farm.cli import app

runner = CliRunner()

def test_check_command_validates_config():
    result = runner.invoke(app, ["check", "--local"])
    assert result.exit_code == 0

When to do: When CLI behavior becomes complex enough to warrant dedicated testing.


4. Add Tests for operations.py (MEDIUM)

Current: Operations module has 30% coverage. Most logic is tested indirectly through test_sync.py.

Potential fix: Add dedicated tests for:

  • up_services() with migration scenarios
  • preflight_check()
  • check_host_compatibility()

When to do: When adding new operations or modifying migration logic.


5. Consider Structured Logging (LOW)

Current: Operations print directly to console using Rich. This couples the operations module to the Rich library.

Potential fix: Use Python's logging module with a custom Rich handler:

import logging

logger = logging.getLogger(__name__)

# In operations:
logger.info("Migrating %s from %s to %s", service, old_host, new_host)

# In cli.py - configure Rich handler:
from rich.logging import RichHandler
logging.basicConfig(handlers=[RichHandler()])

Benefits:

  • Operations become testable without capturing stdout
  • Logs can be redirected to files
  • Log levels provide filtering

When to do: Only if console output coupling becomes a problem for testing or extensibility.


Design Decisions to Keep

These patterns are working well and should be preserved:

  1. asyncio + asyncssh - Solid async foundation
  2. Pydantic models - Clean validation
  3. Rich for output - Good UX
  4. Test structure - Good coverage
  5. Module separation - cli/operations/executor/compose pattern
  6. KISS principle - Don't over-engineer