Skip to content

Refactor python-cli-hexagonal per modularity review findings#1

Open
deimagjas wants to merge 2 commits into
mainfrom
refactor/modularity-review-findings
Open

Refactor python-cli-hexagonal per modularity review findings#1
deimagjas wants to merge 2 commits into
mainfrom
refactor/modularity-review-findings

Conversation

@deimagjas
Copy link
Copy Markdown
Owner

Contexto

Implementa las cuatro observaciones del modularity review (2026-05-29) sobre la plantilla python-cli-hexagonal. El review encontró que la plantilla es sólida como ports & adapters, pero que sus patrones por defecto empujaban a los adopters hacia acoplamiento desbalanceado al crecer.

Cambios

  • Dominio en capas (estilo Bancolombia)domain/model/<feature>/ contiene el modelo + su gateway (puerto); domain/usecase/<feature>/ contiene el caso de uso puro + sus errores. Resuelve la baja cohesión de los antiguos archivos planos (models.py/ports.py/use_cases.py).
  • Use cases purosgreet(request) -> Greeting ya no entrega IO; la entrega por el puerto ocurre en el entrypoint/.
  • Composition root únicowiring.build_dependencies() construye un contenedor Dependencies una sola vez en el callback raíz de cli.py, inyectado vía ctx.obj. Los comandos ya no construyen dependencias.
  • Type checking con ty — verifica que los adapters satisfacen estructuralmente el Protocol de su gateway (pre-commit + CI). Cierra el hueco donde un cambio de firma de un puerto pasaba lint/tests y solo fallaba en runtime.

tach.toml ahora fuerza el flujo usecase → model → kernel compartido. README y CLAUDE.md actualizados (arquitectura, receta para añadir comandos, invariantes).

Verificación

app greet --name Mundo   → "Hola, Mundo!"  exit 0
app greet --name "   "   → "Error: ..."     exit 1
pytest                   → 6 passed
ty check                 → All checks passed!
ruff check / format      → clean
tach check               → All modules validated!
uv build                 → wheel + sdist OK

Comprobación del contrato (prueba de la observación #1): renombrar GreeterPort.deliver hace que ty check falle en el sitio de uso y en wiring.py (ConsoleGreeter no asignable al protocolo) — algo que antes pasaba CI en silencio.

🤖 Generated with Claude Code

Carlos Alberto Argüello Ramírez and others added 2 commits May 29, 2026 21:10
Implements the four findings from the 2026-05-29 modularity review:

- Layered domain (Bancolombia-style): domain/model/<feature>/ holds the
  model + its gateway (port); domain/usecase/<feature>/ holds the pure
  use case + its errors. Fixes low cohesion of the old flat files.
- Pure use cases: greet(request) -> Greeting no longer performs IO;
  delivery via the port happens in the entrypoint.
- Single composition root: wiring.build_dependencies() builds a
  Dependencies container once in the cli root callback, injected via
  ctx.obj; commands no longer construct dependencies.
- Type checking with ty: verifies adapters structurally satisfy their
  gateway Protocol (pre-commit + CI). Closes the gap where a port
  signature change passed lint/tests and only broke at runtime.

tach.toml now enforces usecase -> model -> shared-kernel boundaries.
README and CLAUDE.md updated (architecture, recipe, invariants).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- wiring.Dependencies is now a frozen pydantic BaseModel instead of a
  dataclass (we are in the entrypoint, external deps are allowed here).
  It validates on construction that each injected port conforms to its
  Protocol, so a bad adapter is rejected at the composition root.
- GreeterPort is marked @runtime_checkable to enable that isinstance
  validation; ty still verifies structural conformance statically.
- Add pydantic>=2.0 as a runtime dependency.
- Add tests/unit/test_wiring.py covering validation and immutability.
- Remove the generated modularity-review.html (keep the .md).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@deimagjas
Copy link
Copy Markdown
Owner Author

Comentarios atendidos en e509bcd:

  • modularity-review.html → eliminar: archivo borrado del PR (se conserva la versión .md, que se renderiza en GitHub).
  • Dependencies → usar pydantic: wiring.Dependencies ahora es un BaseModel inmutable (frozen=True, arbitrary_types_allowed=True) en lugar de dataclass. Estamos en el entrypoint/, donde se permiten dependencias externas. Como bonus valida en construcción que cada puerto inyectado cumpla su Protocol: un adapter incorrecto se rechaza en el composition root. Para habilitar esa validación por isinstance, GreeterPort se marcó @runtime_checkable (la verificación estática estructural la sigue haciendo ty). Añadí pydantic>=2.0 como dependencia de runtime y tests/unit/test_wiring.py.

Verificación: pytest (9 passed), ty check, ruff, tach y uv build en verde.

Nota: tu review sigue en estado Pending (los comentarios son borradores). No puedo marcar los hilos como Resolved hasta que envíes el review desde GitHub; una vez enviado, puedo resolverlos.

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.

1 participant