Files
connpy/PLAN_CAPA_SERVICIOS.md
T

8.4 KiB

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á:

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.