Skip to main content

Nexus Validation Engine (motor dinámico de validaciones)

6.1 Responsabilidad​

El Nexus Validation Engine valida una solicitud canónica en base a reglas configurables por partnerId, companyId, sourceSystem, documentType, con versionado, prioridad y activación/desactivación. Es síncrono y corre en proceso con la Purchase Order API.

6.2 Entradas / Salidas​

  • Entradas: (CanonicalPurchaseOrder canonical, PartnerContext partnerContext)
  • Salidas: ValidationResult { valid, errors[{ruleCode, field, message, params, severity}], metadata[ruleCount, durationMs, evaluatedVersions[]] }

6.3 Comparativa de estrategias​

EstrategiaVentajasInconvenientes
Specification Pattern (Java)Fuerte tipado, performance, testableMenos configurable por negocio
Strategy PatternReglas por clase Spring BeanCada regla nueva = nuevo deploy
Chain of ResponsibilityOrdenamiento fácilAcoplamiento por código
Rules Engine (Drools)PotenteOverkill, curva alta, riesgo ejecución arbitraria
Expression Language (SpEL/OGNL)Flexibilidad altaPermite ejecución arbitraria → riesgo seguridad
Nexus Rule DSL + PostgreSQL configExpresivo, seguro, parametrizable, versionableToca implementar parser/evaluador

6.3.1 Estrategia recomendada​

Decisión

Nexus Rule DSL (subset declarativo, sin loops ni invocaciones arbitrarias) + Specification/Strategy para reglas que requieren lookup externo (catálogos) almacenadas en PostgreSQL con validation_rule + validation_rule_version.

  • En código: funciones puras predefinidas (required, minLength, regex, lookupSupplier, currencyAllowed, amountRange) + operadores booleanos (&&, ||, !) + literales string/number/boolean + path JSON ($.header.currency.code).
  • En configuración PostgreSQL: la expresión, prioridad, alcance (partnerId, companyId, sourceSystem, documentType[]), versión y severidad.
  • NO permitir ejecución de código arbitrario (no EL genérico). Parser en Java (ANTLR o parser de expresiones pequeño y a medida con gramática acotada).

6.3.2 Flujo interno del motor​

6.4 Estructura de una validación almacenada (PostgreSQL)​

  • validation_rule: id, code, partnerId, companyId, sourceSystem, documentTypes[], groups[], priority, status, createdBy, createdAt, updatedAt.
  • validation_rule_version: id, rule_id FK, major, minor, status, expression, errorTemplate JSONB, params JSONB, audit, publishedBy, publishedAt.

6.5 Diseño por capas (Arquitectura Base Backend Alpura)​

Alineado a la nueva arquitectura interna Nexus, el Validation Engine se divide en las siguientes capas y clases:

CapaClases / InterfacesMétodos (nomenclatura Alpura)
WEB (Config API, para administrar reglas)ValidationRuleControllercreateValidationRule(), updateRuleVersion(), changeRuleStatus(), searchRules(), dryRunValidation()
Facade (Service Interfaces)ValidationFacade (interface)validatePurchaseOrder(), dryRun(), getRuleById()
Service (implementaciones)ValidationEngineServiceImpl, RuleCacheService, RuleDslEvaluatorPúblicos: validatePurchaseOrder(), reloadCache(), getRuleById(), dryRun()
Privados (prefijo _): _loadActiveRulesByScope(), _sortByGroupPriority(), _prepareRuleParameters(), _evaluateRuleVersion(), _shortCircuitBySeverity(), _buildValidationResult(), _persistValidationResult()
PersistenceValidationRuleRepository, ValidationRuleVersionRepository, ValidationResultRepositoryPúblicos: findActiveRulesByScope(), saveVersion(), findRuleById(), saveResult()
Privados (en implementación): _buildScopeQuery(), _mapToEntity(), _mapToDto()
Commonscommons.valueobjects (RuleCode, ValidationScope, RuleExpression, ValidationSeverity) commons.exceptions (ValidationRuleNotFoundException, InvalidRuleDslException) commons.constants (ValidationGroups, ValidationStages)

6.5.1 Interfaces y firmas (ejemplo)​

// Facade (Service Interface) - capa Facade
public interface ValidationFacade {
ValidationResult validatePurchaseOrder(CanonicalPurchaseOrder canonical, ValidationScope scope);
ValidationResult dryRun(CanonicalPurchaseOrder canonical, UUID ruleVersionId);
ValidationRuleView getRuleById(UUID ruleId);
}

// Service - publicos sin prefijo; internos con _
@Service
public class ValidationEngineServiceImpl implements ValidationFacade {

@Override
public ValidationResult validatePurchaseOrder(CanonicalPurchaseOrder canonical, ValidationScope scope) {
List<ValidationRuleVersion> rules = _loadActiveRulesByScope(scope);
_sortByGroupPriority(rules);
ValidationExecutionContext ctx = _prepareRuleParameters(canonical, scope);
for (ValidationRuleVersion rule : rules) {
_evaluateRuleVersion(rule, ctx);
if (_shortCircuitBySeverity(ctx, rule.severity())) break;
}
ValidationResult result = _buildValidationResult(ctx, rules.size());
_persistValidationResult(scope, canonical.requestId(), result);
return result;
}

// ... otros publics: dryRun, getRuleById ...

private List<ValidationRuleVersion> _loadActiveRulesByScope(ValidationScope scope) { /* ... */ }
private void _sortByGroupPriority(List<ValidationRuleVersion> rules) { /* ... */ }
private ValidationExecutionContext _prepareRuleParameters(CanonicalPurchaseOrder po, ValidationScope scope) { /* ... */ }
private void _evaluateRuleVersion(ValidationRuleVersion rule, ValidationExecutionContext ctx) { /* ... */ }
private boolean _shortCircuitBySeverity(ValidationExecutionContext ctx, ValidationSeverity s) { /* ... */ }
private ValidationResult _buildValidationResult(ValidationExecutionContext ctx, int ruleCount) { /* ... */ }
private void _persistValidationResult(ValidationScope scope, UUID requestId, ValidationResult r) { /* ... */ }
}

// Persistence (Repository Spring Data)
public interface ValidationRuleVersionRepository extends JpaRepository<ValidationRuleVersion, UUID> {
List<ValidationRuleVersion> findActiveByScope(ValidationScope scope);
}

6.6 Evaluación y comparación final​

Tipo de reglaImplementación
EstructuralesOpenAPI + Jakarta validation (REST controller) + reglas internas para arrays/mapas
SintácticasBuilt-in functions pattern, email, dateFormat, currencyISO
SemánticasLookups por catálogos (DB, Redis cache) por partner
NegocioCombinación de funciones, expresiones y prioridades

6.6 Resultado (Response ValidationResult)​

{
"valid": false,
"ruleCount": 23,
"durationMs": 42,
"errors": [
{
"ruleCode": "SUPPLIER_REQUIRED",
"severity": "ERROR",
"field": "supplier.code",
"message": "El proveedor es obligatorio",
"params": {}
}
],
"warnings": []
}

6.7 Consideraciones​

  • Cache: reglas activas (por partner/docType) en caché local (Caffeine) TTL configurable; invalidación por API PATCH status (o webhook event).
  • Testing de reglas: antes de publicar una nueva versión, API de dry-run para ejecutar la regla sobre una solicitud canónica de prueba.
  • No mezclar: lógica de procesamiento de creación de OC dentro de reglas; estas validan y nada más.
  • Observabilidad: histograma nexus_validation_duration_seconds + nexus_validation_failed_total{ruleCode, partnerId}.