Files
connpy/PLAN_CAPA_SERVICIOS.md
T

124 lines
8.4 KiB
Markdown
Raw Normal View History

# Plan de Arquitectura: Creación de Capa de Servicios en Connpy
Este documento detalla el plan paso a paso para refactorizar `connpy` y extraer la lógica de negocio actual (acoplada en `connapp.py` y `api.py`) hacia una **Capa de Servicios (Service Layer)** limpia y reutilizable.
## 🎯 Objetivos
1. **Desacoplar la CLI (`connapp.py`)**: La CLI solo debe encargarse de procesar argumentos (`argparse`), solicitar datos al usuario (`inquirer`, `rich.prompt`) y renderizar la salida en pantalla (`rich`).
2. **Desacoplar la API (`api.py`)**: La API actual (Flask) y la futura API gRPC solo deben encargarse de exponer endpoints y delegar la ejecución a la capa subyacente.
3. **Centralizar la Lógica de Negocio**: Todas las operaciones sobre nodos, perfiles, configuración, ejecución de comandos, IA, plugins e importación/exportación vivirán en la nueva capa de servicios. Esto asegura que ejecutar una acción desde la CLI local, CLI remota, o API produzca **exactamente el mismo comportamiento**.
---
## 🏗️ 1. Estructura de la Capa de Servicios
Crearemos un nuevo paquete `connpy/services/` que agrupe las distintas responsabilidades del dominio. Basado en todos los comandos de `connapp.py`, la estructura será:
```text
connpy/
└── services/
├── __init__.py
├── node_service.py # CRUD de nodos, carpetas, bulk, mover, copiar y listar
├── profile_service.py # CRUD de perfiles
├── execution_service.py # Ejecución de comandos en paralelo (ad-hoc, scripts, yaml, test)
├── import_export_service.py# Importación y exportación de configuración a YAML
├── ai_service.py # Interacciones con el Agente (Claude/LLMs) y su configuración
├── plugin_service.py # Habilitar, deshabilitar y listar plugins
├── config_service.py # Manejo de la configuración global de la app (case, fzf, idletime)
├── system_service.py # Control de ciclo de vida (iniciar/detener API local)
└── exceptions.py # Excepciones de negocio (ej. NodeNotFoundError)
```
---
## 🛠️ 2. Diseño de los Servicios (Casos de Uso Completos)
A continuación, la lista detallada de servicios mapeando cada funcionalidad de la aplicación actual:
### 1. `NodeService`
Maneja toda la interacción con `configfile` relacionada con la topología de red (nodos y carpetas).
- `list_nodes(filter: str/list) -> list`: Devuelve lista de nodos (comando `list`).
- `list_folders(filter: str/list) -> list`: Devuelve lista de carpetas.
- `get_node_details(unique: str) -> dict`: Devuelve configuración de un nodo (`node show`).
- `add_node(unique: str, data: dict) -> None`: Agrega un nuevo nodo (`node -a`).
- `update_node(unique: str, data: dict) -> None`: Modifica un nodo (`node -e`).
- `delete_node(unique: str) -> None`: Elimina un nodo (`node -r`).
- `move_node(src: str, dst: str) -> None`: Renombra o mueve nodos a otras carpetas (`move`).
- `copy_node(src: str, dst: str) -> None`: Duplica un nodo existente (`copy`).
- `bulk_add_nodes(folder: str, nodes_data: list) -> dict`: Lógica para procesar la creación masiva de nodos (`bulk`).
### 2. `ProfileService`
- `list_profiles() -> list`: Muestra los perfiles disponibles (`list`).
- `get_profile(name: str) -> dict`: Muestra un perfil (`profile show`).
- `add_profile(name: str, data: dict) -> None`: Agrega un perfil (`profile -a`).
- `update_profile(name: str, data: dict) -> None`: Modifica un perfil (`profile mod`).
- `delete_profile(name: str) -> None`: Elimina un perfil (`profile -r`).
### 3. `ExecutionService`
Encapsula la clase `core.nodes` para conexiones y envíos de comandos, abstrayéndola de `sys.stdout` o funciones `print`.
- `run_commands(nodes_list: list, commands: list) -> dict`: Llama a nodos en paralelo y devuelve un diccionario con los resultados (`run`).
- `test_commands(nodes_list: list, commands: list, expected: str) -> dict`: Valida el output esperado.
- `run_cli_script(nodes_list: list, script_path: str) -> dict`: Lee y ejecuta un script plano en los nodos.
- `run_yaml_playbook(playbook_path: str) -> dict`: Ejecuta la lógica compleja definida en un archivo YAML.
### 4. `ImportExportService`
- `export_to_yaml(folder_name: str, output_path: str) -> None`: Exporta la configuración completa de una carpeta de forma segura (`export`).
- `import_from_yaml(yaml_path: str, destination_folder: str) -> dict`: Parsea e importa nodos desde un archivo YAML asegurando que no haya colisiones críticas (`import`).
### 5. `PluginService`
- `list_plugins() -> list`: Devuelve el estado de todos los plugins detectados (activos/inactivos) (`plugin`).
- `enable_plugin(name: str) -> None`: Activa un plugin en la configuración.
- `disable_plugin(name: str) -> None`: Desactiva un plugin en la configuración.
### 6. `ConfigService`
- `update_setting(key: str, value: any) -> None`: Actualiza de forma genérica o específica (fzf, case, idletime, configfolder) en el `configfile` (`config`).
- `get_settings() -> dict`: Devuelve las configuraciones globales actuales.
### 7. `AIService`
Encapsula `connpy.ai.ai`.
- `ask(input_text: str, dryrun: bool, chat_history: list) -> dict/str`: Envia consulta al Agente (`ai`).
- `confirm(input_text: str) -> bool`: Mecanismo de seguridad.
- `configure_provider(provider: str, model: str, api_key: str) -> None`: Guarda configuración de OpenAI/Anthropic/Google en config (`config openai/anthropic/google`).
### 8. `SystemService`
- `start_api(host: str, port: int) -> None`: Levanta el daemon o proceso de la API (`api start`).
- `stop_api() -> None`: Baja el proceso local (`api stop`).
- `status_api() -> dict`: Devuelve el estado del proceso local.
---
## 🔌 3. Sobre los Plugins (Core Plugins)
Los plugins de core (como `sync.py`) añaden sus propios `subparsers` directamente a la CLI (ej. `sync start`, `sync backup`, `sync restore`).
- **Arquitectura para Plugins**: Para mantener la capa de servicios limpia, los plugins deben instanciar su propio Service si requieren lógica compleja (ej. `GoogleSyncService` definido dentro de `core_plugins/sync.py`), o bien llamar a los servicios core que definimos arriba. El motor de plugins de la aplicación no se toca, pero el comportamiento dentro de los plugins debería alinearse a usar llamadas de la Capa de Servicios si tocan datos de nodos.
---
## 🚀 4. Fases de Implementación Actualizadas
### Fase 1: Creación del Esqueleto y Modelos de Datos
1. Crear el directorio `connpy/services/` y los archivos listados.
2. Definir `exceptions.py` con errores como `NodeNotFoundError`, `ProfileNotFoundError`, `DuplicateEntityError`.
3. Crear el `connpy/services/__init__.py` que expondrá estos servicios para que puedan ser fácilmente importados (`from connpy.services import NodeService, ExecutionService`).
### Fase 2: Migración de CRUD y Configuración
1. Refactorizar la CLI y la API para instanciar y usar: `NodeService`, `ProfileService`, `ConfigService` y `PluginService`.
2. Todo el código de validación de variables (`_questions_nodes`, `_type_node`) permanecerá en `connapp.py` ya que pertenece a la "Presentación/CLI", pero los diccionarios limpios se pasarán al Servicio para su guardado final.
### Fase 3: Migración de Import/Export e IA
1. Extraer la lógica de YAML a `ImportExportService`.
2. Mover la configuración de las llaves API a `AIService`.
### Fase 4: Migración de Ejecución (El cambio más complejo)
1. Desacoplar `core.nodes` para que sea capaz de retornar estado consolidado (diccionarios con la salida de los comandos por nodo) en vez de imprimir asíncronamente en pantalla con `printer`.
2. Integrar `ExecutionService` en los comandos `run`, `node (connect)`, test, etc.
3. La CLI se subscribirá a los resultados que devuelve el `ExecutionService` para formatearlos con `rich`.
### Fase 5: Preparación para Cliente Servidor (gRPC/REST remoto)
1. Con los servicios totalmente aislados, si la CLI opera en "modo remoto", inyectará un Cliente Remoto que implementa las mismas interfaces (mismos métodos del `NodeService`) pero que serializa peticiones hacia la API en lugar de acceder directamente al archivo de configuración cifrado local.
---
## ✅ Checklist para el éxito
- [ ] Ningún `print()`, `console.print()`, `Prompt.ask()` debe existir dentro del paquete `services/`.
- [ ] Todas las excepciones lanzadas por `services/` deben ser manejadas visualmente por la capa que los consuma (`connapp.py` las pinta, `api.py` devuelve 400/500 JSON).
- [ ] Asegurarse de que el comportamiento local (CLI sin red) no perciba pérdida de rendimiento.