Skip to content

refactor: [#298] Phase 3 Port Topology Domain Model - Foundation (P3.1-P3.3)#299

Merged
josecelano merged 2 commits into
mainfrom
298-phase-3-port-topology
Jan 25, 2026
Merged

refactor: [#298] Phase 3 Port Topology Domain Model - Foundation (P3.1-P3.3)#299
josecelano merged 2 commits into
mainfrom
298-phase-3-port-topology

Conversation

@josecelano

Copy link
Copy Markdown
Member

Summary

This PR implements the domain layer foundation for Phase 3 of the Docker Compose Topology refactoring (Epic #287). It creates type-safe port binding types and cross-service port conflict validation, following the same pattern established for networks in Phase 1-2.

Changes

P3.1: Create Port Domain Types ✅

  • Reused Protocol enum from domain/tracker/protocol.rs (no duplication)
  • Created PortBinding struct with:
    • Private fields: host_port, container_port, protocol, host_ip, description
    • Convenience constructors: tcp(), udp(), localhost_tcp()
    • docker_compose_binding() method for YAML rendering (e.g., "6969:6969/udp")
    • Description support for inline YAML comments
  • 10 unit tests covering all constructors and binding formats

P3.2: Extend ServiceTopology with Ports ✅

  • Added ports: Vec<PortBinding> field to ServiceTopology
  • Added new() constructor with ports parameter
  • Added with_networks() for backward compatibility
  • Made all fields private with getters (DDD compliance)
  • 4 new tests for port functionality

P3.3: Add Cross-Service Port Validation ✅

  • Created PortConflict error type with full context (both services, both bindings)
  • Created TopologyError enum with help() method (DDD error conventions)
  • Implemented validate_port_uniqueness() on DockerComposeTopology
  • 6 tests covering conflict scenarios

DDD Compliance

All code follows the project's DDD practices (docs/contributing/ddd-practices.md):

Requirement Status
Private fields with getters ✅ All domain types
#[must_use] on getters ✅ Applied
Expressive errors with help() TopologyError::help()
Single point of validation ✅ Aggregate method

What's NOT in this PR

P3.4 (Template Integration) will be a follow-up PR:

  • Update DockerComposeContext to use derived ports
  • Simplify docker-compose.yml.tera port sections
  • Remove conditional port logic from template

This split allows the domain foundation to be reviewed and merged independently.

Testing

  • ✅ 20 new unit tests for port topology
  • ✅ All 2021 existing tests pass
  • ✅ All 393 doc tests pass
  • ✅ E2E infrastructure lifecycle tests pass
  • ✅ E2E deployment workflow tests pass
  • ✅ All linters pass (clippy, rustfmt, markdown, yaml, toml, cspell, shellcheck)

Files Changed

File Change
src/domain/topology/port.rs NEW - PortBinding struct
src/domain/topology/error.rs NEW - PortConflict, TopologyError
src/domain/topology/aggregate.rs Extended ServiceTopology with ports
src/domain/topology/mod.rs Added module exports
docs/issues/287-*.md Updated epic with PR split
docs/issues/298-*.md Updated spec with completed tasks

Related

…alidation

This commit implements Phase 3 (P3.1-P3.3) of Epic #287:

P3.1 - Create Port domain types:
- Add PortBinding struct with protocol, host/container ports, host IP, description
- Reuse existing Protocol enum from domain/tracker
- Add convenience constructors: tcp(), udp(), localhost_tcp()
- Add docker_compose_binding() for YAML rendering

P3.2 - Extend ServiceTopology with ports:
- Add ports: Vec<PortBinding> to ServiceTopology
- Add new() constructor with networks and ports
- Add with_networks() backward-compatible convenience constructor
- Add ports() and has_ports() methods

P3.3 - Add cross-service port validation:
- Add validate_port_uniqueness() to DockerComposeTopology
- Create PortConflict error with actionable details
- Create TopologyError enum for topology validation errors

All PORT-* rules from the original refactoring plan are now representable
as domain types. P3.4 (template integration) is tracked separately.
- Split Phase 3 into two PRs for better reviewability
- PR #298: Domain foundation (P3.1-P3.3) - this PR
- Follow-up PR: Template integration (P3.4)
- Mark P3.1-P3.3 tasks as complete in spec
- Update acceptance criteria to show partial completion
- Add key decision about PR split and DDD error conventions
@josecelano josecelano self-assigned this Jan 25, 2026
@josecelano

Copy link
Copy Markdown
Member Author

ACK dd3ab12

@josecelano josecelano merged commit 24b3185 into main Jan 25, 2026
38 checks passed
@josecelano josecelano linked an issue Jan 25, 2026 that may be closed by this pull request
21 tasks
josecelano added a commit that referenced this pull request Jan 25, 2026
- Create spec for P3.4 template integration
- Update epic to mark #298 as complete with PR #299
- Add #300 as new task for template integration
- Link spec to GitHub issue #300
josecelano added a commit that referenced this pull request Jan 26, 2026
…on (P3.4)

8dcbbc9 fix: [#300] workaround for clippy large_stack_arrays by commenting tests (Jose Celano)
3ea8c65 refactor: [#300] Phase 3 Port Topology Template Integration (P3.4) (Jose Celano)

Pull request description:

  ## Summary

  Integrates the `PortBinding` domain types (created in #298) into the Docker Compose template rendering, completing Phase 3 of the topology refactoring.

  ## Changes

  ### Port Derivation (`port_derivation.rs`)
  - Implements PORT-01 through PORT-11 rules from the refactoring plan
  - `derive_tracker_ports()` - UDP always exposed, HTTP/API only when no TLS
  - `derive_caddy_ports()` - Always exposes 80, 443, 443/udp
  - `derive_prometheus_ports()` - 9090 on localhost only
  - `derive_grafana_ports()` - 3000 only when no TLS
  - `derive_mysql_ports()` - No exposed ports

  ### Port Definition DTO (`port_definition.rs`)
  - `PortDefinition` struct with `binding` and `description` fields
  - `From<&PortBinding>` trait for idiomatic conversion
  - Serializable for Tera template context

  ### Service Context Updates
  - Added `ports: Vec<PortDefinition>` to all service configs
  - Tracker, Caddy, Prometheus, Grafana, MySQL all derive their ports

  ### Template Simplification (`docker-compose.yml.tera`)
  - Replaced conditional port logic with simple loops
  - Added description comments using `# {{ port.description }}`
  - Removed `needs_ports_section` checks (replaced by `ports | length > 0`)

  ### Validation
  - `try_build()` method on `DockerComposeContextBuilder` validates port uniqueness
  - `PortConflictError` with `help()` method for actionable error messages
  - DDD "always valid" pattern: `DockerComposeTopology::new()` returns `Result`

  ### Documentation
  - Created Issue #301 spec for Phase 4 (DDD Layer Alignment - future work)
  - Updated Epic #287 with Phase 4 tasks

  ## Testing

  - 15 unit tests for port derivation functions
  - Unit tests for `PortDefinition` conversion
  - Tests for `try_build()` validation
  - All E2E tests pass

  ## Rendered Output Example

  ```yaml
  services:
    tracker:
      ports:
        # BitTorrent UDP announce
        - "6969:6969/udp"
        # HTTP tracker announce
        - "7070:7070"
        # HTTP API (stats/whitelist)
        - "1212:1212"
  ```

  ## Checklist

  - [x] Pre-commit checks pass: `./scripts/pre-commit.sh`
  - [x] All PORT-* rules implemented in port derivation functions
  - [x] Port descriptions render as YAML comments in generated output
  - [x] Template uses loops over `service.ports`
  - [x] Cross-service port conflicts detected before rendering via `try_build()`
  - [x] All existing E2E tests pass

  ## Related

  - **Issue**: #300
  - **Epic**: #287 - Docker Compose Topology Domain Model Refactoring
  - **Foundation PR**: #299 (merged)
  - **Spec**: `docs/issues/300-phase-3-port-topology-template-integration.md`

ACKs for top commit:
  josecelano:
    ACK 8dcbbc9

Tree-SHA512: 2a71fd13df0353e57d5800210a9e4aeded11e1550f6a5c65b4ff3ae50b6cd111389cc9504bdff4229681c79cbfc639e55720f69e4f4d402d2cbfe5fdaae1ebaf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Refactor] Phase 3: Create Port Topology Domain Model

1 participant