ADR-004: Arquitectura Hexagonal (Puertos y Adaptadores)¶
Estado¶
Aceptada
Fecha¶
2024-03-01
Contexto¶
Voice2Machine comenzó como un script monolítico de ~200 líneas. Al crecer en funcionalidad, enfrentamos problemas típicos de código acoplado:
- Testing difícil: Mocks de GPU, audio, API externa
- Cambios cascada: Modificar Whisper requería tocar 5+ archivos
- Vendor lock-in: Cambiar de Ollama a Gemini requería refactor masivo
- Responsabilidades difusas: No estaba claro dónde poner nueva lógica
Requisitos:¶
- Núcleo de negocio agnóstico a frameworks
- Adaptadores intercambiables (ej: cambiar Whisper por otro ASR)
- Testabilidad sin hardware real
- Boundaries claros entre capas
Decisión¶
Adoptar Arquitectura Hexagonal (Ports & Adapters) como patrón estructural.
Estructura de carpetas:¶
src/v2m/
├── core/ # Configuración, logging, interfaces base
├── domain/ # Modelos, puertos (interfaces), errores
├── services/ # Orchestrator, coordinación
└── infrastructure/ # Adaptadores (Whisper, Audio, LLM)
Implementación de puertos:¶
from typing import Protocol, runtime_checkable
@runtime_checkable
class TranscriptionService(Protocol):
async def transcribe(self, audio: bytes) -> str: ...
Los adaptadores implementan los puertos:
class WhisperAdapter:
async def transcribe(self, audio: bytes) -> str:
# Implementación concreta con faster-whisper
Consecuencias¶
Positivas¶
- ✅ Testing aislado: Tests unitarios sin GPU ni red
- ✅ Flexibilidad: Cambiar Gemini por Ollama es editar 1 archivo
- ✅ Onboarding: Estructura predecible y documentada
- ✅ Type safety:
Protocol+ mypy detecta incompatibilidades en compile time
Negativas¶
- ⚠️ Más archivos: ~20 archivos vs ~5 del script original
- ⚠️ Indirección: Hay que navegar entre capas para entender flujo completo
- ⚠️ Overhead inicial: Setup más complejo para features simples
Alternativas Consideradas¶
Clean Architecture (Uncle Bob)¶
- Rechazado: Demasiadas capas (Entities, Use Cases, Interface Adapters, Frameworks) para el scope.
MVC/MVP¶
- Rechazado: Orientado a UI, no aplica bien a un daemon backend.
Simple Modules¶
- Rechazado: En la práctica volvíamos al acoplamiento original.