nAIxus Docs

Architecture backend

Détail de l'architecture hexagonale du service Core, couches, ports, adapters et patterns d'implémentation.

Le service Core suit strictement l'architecture hexagonale (Ports & Adapters). Ce document détaille chaque couche, ses responsabilités, et les patterns d'implémentation.

Structure des couches

services/core/src/
├── domain/           # Entités, services domaine, règles métier pures
├── application/      # Use cases, ports (outbound), services d'application
│   ├── ports/
│   │   └── outbound/ # Interfaces (Protocol) que l'infra doit implémenter
│   ├── services/
│   │   └── executors/ # Node executors (Agent, Condition, Loop, etc.)
│   └── use_cases/    # Orchestration des opérations métier
├── infrastructure/   # Adapters concrets
│   ├── persistence/  # SQLAlchemy models, repositories, session, UoW
│   ├── llm/          # Adapters LLM (OpenAI, Bedrock, Ollama, etc.)
│   ├── secrets/      # Chiffrement Fernet
│   ├── logging/      # structlog adapter
│   ├── observability/ # OpenTelemetry tracing + metrics
│   └── di/           # Conteneur d'injection de dépendances
└── interfaces/       # Couche HTTP
    ├── api/
    │   ├── routes/   # Endpoints FastAPI
    │   ├── schemas/  # Pydantic request/response DTO
    │   └── dependencies/ # Auth, tenant context
    └── middleware/

Le domaine

Le domaine contient les entités et les règles métier pures. Aucune dépendance externe — juste du Python standard.

Entités principales :

EntitéResponsabilité
FlowGraphe de nodes avec leurs connexions (edges)
NodeBloc fonctionnel (Agent, Condition, etc.) avec sa configuration
RunInstance d'exécution d'un flow avec son état
ChannelPoint de déploiement d'un flow
TenantEspace isolé d'un client

La couche application

Ports (interfaces)

Les ports définissent ce dont l'application a besoin sans savoir comment c'est implémenté. Ils sont tous dans application/ports/outbound/ et utilisent Protocol (Python typing).

Principaux ports :

PortResponsabilité
IFlowRepositoryCRUD des flows
IRunRepositoryGestion des exécutions
IChannelRepositoryGestion des channels
ITenantRepositoryGestion des tenants
IApiKeyRepositoryClés API par channel
ISecretRepositorySecrets chiffrés
IFlowTemplateRepositoryTemplates réutilisables
IWorkspaceAISettingsRepositoryConfiguration IA par workspace
IEventStoreJournal d'événements (audit)
IMetricsPortCollection de métriques
LLMPortAppel aux fournisseurs LLM
LoggerPortLogging structuré
SecretsPortRécupération de secrets (partagé)

Use cases

Chaque opération métier est un use case isolé. Pattern standard :

class CreateFlowUseCase:
    def __init__(self, flow_repo: IFlowRepository, logger: LoggerPort):
        self._flow_repo = flow_repo
        self._logger = logger

    async def execute(self, tenant_id: str, data: CreateFlowInput) -> Flow:
        # Validation métier
        # Appel aux ports
        # Retour du résultat domaine

Les use cases ne connaissent que les ports, jamais les adapters concrets. L'injection de dépendances (via le DIContainer) fournit les implémentations.

Node executors

Les executors sont dans application/services/executors/. Chaque type de node a son executor :

ExecutorNodeRôle
AgentNodeExecutorAgentAppel LLM avec prompt, tools, streaming
CodeExecutorCodeExécution Python sandboxée
ConditionExecutorConditionÉvaluation d'expressions, branchement
HTTPRequestExecutorHTTP RequestAppel API externe
HumanInputExecutorHuman InputPause en attente de saisie humaine
LoopContainerExecutorLoopItération sur une liste
ParseDocExecutorParse DocumentExtraction de texte (PDF, DOCX)
MergeDocsExecutorMerge DocumentsFusion de plusieurs documents
SetVariableExecutorSet VariableDéfinition de variable de contexte
DirectMessageExecutorDirect MessageEnvoi de message direct
TriggerFromChannelExecutorTrigger ChannelDéclenchement depuis un channel
ManualTriggerExecutorTrigger ManualDéclenchement manuel

Le NodeExecutorRegistry (dans application/services/) résout le bon executor en fonction du node_type.

Pour les détails de chaque executor, voir Référence des node executors.

L'infrastructure

Persistence (SQLAlchemy)

  • ORM : SQLAlchemy 2 en mode async (asyncpg pour PostgreSQL, aiosqlite pour SQLite dev).
  • Pattern : Repository + Unit of Work.
  • Migrations : Alembic (dans services/core/alembic/).
  • Multi-tenant : Chaque modèle porte un tenant_id. Les repositories filtrent automatiquement.

Modèles principaux : FlowModel, RunModel, ChannelModel, TenantModel, UserModel, ApiKeyModel, SecretModel, FlowTemplateModel, PolicyModel, EventModel, AgentTemplateModel, WorkspaceAISettingsModel.

LLM adapters

Chaque fournisseur LLM a son adapter dans infrastructure/llm/ qui implémente le LLMPort :

AdapterFournisseur
OpenAIAdapterOpenAI (GPT-4o, GPT-4 Turbo)
AzureOpenAIAdapterAzure OpenAI (accès entreprise)
BedrockAdapterAWS Bedrock (Claude, Titan, Llama)
WatsonXAdapterIBM WatsonX (Granite, Llama)
OllamaAdapterOllama (modèles locaux)
VLLMAdaptervLLM (modèles auto-hébergés)

Le LLMAdapterFactory résout le bon adapter à partir de la configuration du workspace et des secrets.

Secrets

Les secrets (clés API, credentials) sont chiffrés au repos avec Fernet (AES-128). L'adapter DatabaseSecretsAdapter gère le chiffrement/déchiffrement transparent.

Observabilité

  • Logging : structlog avec le StructlogAdapter qui implémente LoggerPort.
  • Tracing : OpenTelemetry via OTelTracingAdapter.
  • Métriques : LogMetricsAdapter implémente IMetricsPort.

Injection de dépendances

Le DIContainer (infrastructure/di/) câble les ports aux adapters au démarrage. Les routes FastAPI utilisent Depends() pour injecter les use cases configurés.

La couche interfaces

Routes

Les endpoints FastAPI sont dans interfaces/api/routes/. Chaque module correspond à un domaine fonctionnel (flows, runs, channels, tenants, etc.).

Schémas

Les DTOs Pydantic sont dans interfaces/api/schemas/. Ils valident les entrées et formatent les sorties. Ils ne contiennent pas de logique métier.

Dépendances

  • auth.py : Validation JWT, extraction du user, vérification du tenant.
  • tenant.py : Injection du TenantContext dans le cycle de vie de la requête.

Pour aller plus loin

On this page