Arquitectura sin Estado
VCR ejecuta múltiples réplicas de cada aplicación, y puede escalar aún más bajo carga. Las réplicas también pueden reducirse a cero cuando están inactivas. Esto tiene importantes implicaciones para la gestión del estado.
Por qué el estado en memoria no es seguro
Cualquier dato almacenado en memoria (variables globales, objetos a nivel de módulo, cachés en proceso) lo está:
- Local a una sola réplica
- Pérdida al reiniciar o escala a cero
- Invisible para otras réplicas que gestionan solicitudes simultáneas
Nunca des por sentado que dos peticiones van a coincidir en la misma réplica. Si lo hacen hoy, no será así cuando aumente la carga.
Equivocada:
// Replica-local — invisible to other replicas and lost on restart
const cache = {};
cache['userId'] = { name: 'Alice' };
let requestCount = 0;
requestCount++;
Correcto:
import { vcr } from '@vonage/vcr-sdk';
const state = vcr.getInstanceState();
await state.set('userId', { name: 'Alice' });
await state.increment('requestCount', 1);
Elegir el ámbito estatal adecuado
| Alcance | Método de acceso | Visibilidad |
|---|---|---|
| Sesión | new State(session) | Una sola sesión |
| Instancia | vcr.getInstanceState() | Todas las réplicas de esta instancia desplegada |
| Account | vcr.getAccountState() | Todas las aplicaciones de la cuenta de Vonage |
vcr.getInstanceState() es el valor predeterminado correcto para el estado compartido. Se trata de una envoltura práctica que asigna el estado al ID de instancia actual, de modo que todas las réplicas de la misma implementación comparten el mismo almacén.
vcr.getAccountState() es para datos entre aplicaciones, como una lista de bloqueo compartida o una configuración que varias aplicaciones VCR necesitan leer.
Estado de la sesión (new State(session)) es apropiado para datos que pertenecen a una única interacción del usuario, como el contexto de la conversación o el estado de la llamada.
Patrones comunes en memoria que hay que sustituir
| Patrón | Sustitución |
|---|---|
const cache = {} a nivel de módulo | vcr.getInstanceState() |
| Objeto Singleton que contiene el estado de la solicitud | vcr.getInstanceState() o vcr.createSessionWithId(userId) |
global.something = ... | vcr.getInstanceState() |
| Contador incrementado por petición | state.increment('counter', 1) |
Ejemplos de códigos
Node.js - contador compartido entre réplicas:
import { vcr } from '@vonage/vcr-sdk';
const state = vcr.getInstanceState();
// Increment atomically — safe across replicas
await state.increment('requestCount', 1);
const count = await state.get('requestCount');
Node.js - estado de sesión por usuario:
import { vcr, State } from '@vonage/vcr-sdk';
app.post('/message', async (req, res) => {
// Each user gets an isolated state namespace
const session = vcr.createSessionWithId(req.body.userId);
const state = new State(session);
await state.set('lastSeen', new Date().toISOString());
res.sendStatus(200);
});
Python - estado compartido entre réplicas:
from vonage_cloud_runtime.vcr import VCR
vcr = VCR()
state = vcr.getInstanceState()
await state.set('key', 'value')
value = await state.get('key')
Mejores prácticas para la organización de sesiones
Crítico: Uso indebido de vcr.createSession() es una de las fuentes más comunes de errores en las aplicaciones VCR. Comprender el alcance de las sesiones es esencial para crear aplicaciones que escalen correctamente.
El problema con vcr.createSession() en Global Scope
vcr.createSession() genera un aleatorio, efímero cada vez que se llama. Si se llama en el ámbito de módulo/global y se utiliza la sesión resultante con new State(session):
- Cada réplica crea su propia sesión aleatoria al iniciarse
- Los datos escritos por una réplica son invisible a todos los demás
- Al reiniciar o escalar a cero, el identificador de sesión se pierde y los datos quedan huérfanos permanentemente.
- Esto anula por completo el objetivo de utilizar el proveedor estatal de datos compartidos.
Anti-patrón - NO hagas esto:
import { vcr, State } from '@vonage/vcr-sdk';
// WRONG: random session at global scope
const session = vcr.createSession();
const state = new State(session);
app.post('/message', async (req, res) => {
// This state is only accessible by this exact replica, using this exact session ID.
// Other replicas have their own random session and cannot see this data.
await state.set('messageCount', (await state.get('messageCount') || 0) + 1);
res.sendStatus(200);
});
Patrones correctos
Para el estado compartido en todas las réplicas, utilice el estado de instancia
import { vcr } from '@vonage/vcr-sdk';
// Shared across all replicas of this instance — no session needed
const state = vcr.getInstanceState();
app.post('/message', async (req, res) => {
await state.increment('messageCount', 1);
res.sendStatus(200);
});
Para el estado por conversación/por usuario: utilice identificadores de sesión deterministas.
Sesiones de alcance para solicitar contexto utilizando IDs de devoluciones de llamada. Esto garantiza que cualquier réplica que gestione una solicitud posterior para la misma conversación/usuario pueda acceder al mismo estado.
import { vcr, State } from '@vonage/vcr-sdk';
// Voice: scope to the conversation UUID from the callback
app.post('/onCall', async (req, res) => {
const session = vcr.createSessionWithId(req.body.conversation_uuid);
const state = new State(session);
await state.set('step', 'greeting');
await state.set('startTime', Date.now());
res.json([{ action: 'talk', text: 'Welcome!' }]);
});
// SMS: scope to the sender's phone number
app.post('/onMessage', async (req, res) => {
const session = vcr.createSessionWithId(req.body.from);
const state = new State(session);
await state.increment('messageCount', 1);
await state.set('lastMessage', req.body.text);
res.sendStatus(200);
});
La idea clave: el ID de sesión debe ser determinista y derivable del contexto de la solicitud. Los buenos identificadores de sesión son:
| Fuente | Ejemplo ID | Caso práctico |
|---|---|---|
| Retrollamada de voz | conversation_uuid | Estado IVR por llamada |
| Devolución de llamada SMS/MMS | número de teléfono del remitente | Historial de conversaciones por remitente |
| Usuario autenticado | ID de usuario o asunto JWT | Preferencias por usuario |
| Correlación personalizada | ID del pedido, ID del billete | Estado por flujo de trabajo |
Para el registro de suscripciones, utilice la sesión global
vcr.getGlobalSession() devuelve una sesión determinista compartida por todas las réplicas. Utilícela sólo para registrar suscripciones de proveedores (voz, mensajes), no para almacenar el estado de la aplicación.
import { vcr, Voice, Messages } from '@vonage/vcr-sdk';
// Correct: global session for subscription registration
const globalSession = vcr.getGlobalSession();
const voice = new Voice(globalSession);
const messages = new Messages(globalSession);
await voice.onCall('onCall');
await messages.onMessage('onMessage',
{ type: 'sms', number: process.env.VONAGE_NUMBER },
{ type: 'sms', number: undefined }
);
Resumen
| Necesita | Método | Alcance |
|---|---|---|
| Contadores compartidos, cachés, configuración | vcr.getInstanceState() | Todas las réplicas |
| Estado por usuario/por conversación | vcr.createSessionWithId(id) + new State(session) | Determinista, con ámbito de solicitud |
| Datos compartidos entre aplicaciones | vcr.getAccountState() | Todas las aplicaciones en Account |
| Inscripción | vcr.getGlobalSession() | Singleton para toda la instancia |
| Nunca a escala mundial para el Estado | vcr.createSession() | Aleatorio, inalcanzable por otras réplicas |