124 lines
8.4 KiB
Markdown
124 lines
8.4 KiB
Markdown
# 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.
|