Arquitetura sem estado

O VCR executa várias réplicas de cada aplicação e pode ser dimensionado ainda mais sob carga. As réplicas também podem ser reduzidas a zero quando o sistema está ocioso. Isso tem implicações importantes para a forma como você gerencia o estado.

Por que o estado na memória não é seguro

Quaisquer dados armazenados na memória (variáveis globais, objetos no nível do módulo, caches no processo) são:

  • Restrito a apenas uma réplica
  • Perda na reinicialização ou na calibração para zero
  • Invisível para outras réplicas que processam solicitações simultâneas

Nunca presuma que duas solicitações serão direcionadas à mesma réplica. Mesmo que isso aconteça hoje, isso não se manterá à medida que a carga aumentar.

Errado:

// Replica-local — invisible to other replicas and lost on restart
const cache = {};
cache['userId'] = { name: 'Alice' };

let requestCount = 0;
requestCount++;

Correto:

import { vcr } from '@vonage/vcr-sdk';

const state = vcr.getInstanceState();
await state.set('userId', { name: 'Alice' });
await state.increment('requestCount', 1);

Escolhendo o escopo estadual adequado

Âmbito Método de acesso Visibilidade
Sessão new State(session) Apenas uma sessão
Instância vcr.getInstanceState() Todas as réplicas desta instância implantada
Account vcr.getAccountState() Todos os aplicativos na conta da Vonage

vcr.getInstanceState() é a configuração padrão correta para o estado compartilhado. Trata-se de um wrapper de conveniência que restringe o escopo do State ao ID da instância atual, de modo que todas as réplicas da mesma implantação compartilhem o mesmo armazenamento.

vcr.getAccountState() destina-se a dados compartilhados entre aplicativos, como uma lista de bloqueio compartilhada ou uma configuração que vários aplicativos VCR precisam acessar.

Estado da sessão (new State(session)) é adequado para dados que pertencem a uma única interação do usuário, como o contexto de uma conversa ou o status de uma chamada.

Padrões comuns de memória interna a serem substituídos

Padrão Substituição
const cache = {} no nível do módulo vcr.getInstanceState()
Objeto Singleton que armazena o estado da solicitação vcr.getInstanceState() ou vcr.createSessionWithId(userId)
global.something = ... vcr.getInstanceState()
Contador incrementado a cada solicitação state.increment('counter', 1)

Exemplos de código

Node.js — contador compartilhado 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 da sessão por usuário:

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 compartilhado 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')

Melhores práticas para definição do escopo da sessão

Crítico: Uso indevido vcr.createSession() é uma das fontes mais comuns de erros em aplicativos VCR. Compreender o escopo da sessão é essencial para desenvolver aplicativos que se adaptem corretamente.

O problema com vcr.createSession() em âmbito global

vcr.createSession() gera um aleatório, efêmero ID da sessão sempre que for chamada. Se você chamá-la no escopo do módulo/global e usar a sessão resultante com new State(session):

  • Cada réplica cria sua própria sessão aleatória ao iniciar
  • Os dados gravados por uma réplica são invisível a todos os demais
  • Ao reiniciar ou ao zerar a escala, o ID da sessão é perdido e os dados ficam permanentemente órfãos
  • Isso vai totalmente contra o objetivo de usar o provedor State para dados compartilhados

Antipadrão — NÃO faça isso:

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);
});

Padrões corretos

Para um estado compartilhado entre todas as réplicas — use o “Instance State”

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 o estado por conversa/por usuário — use IDs de sessão determinísticas

As sessões do Scope solicitam o contexto por meio de IDs provenientes de callbacks. Isso garante que qualquer réplica que processe uma solicitação subsequente para a mesma conversa/usuário possa acessar o mesmo 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);
});

A principal conclusão: o ID da sessão deve ser determinístico e derivável do contexto da solicitação. Exemplos de bons IDs de sessão incluem:

Fonte ID do exemplo Caso de uso
Retorno de chamada de voz conversation_uuid Estado do IVR por chamada
Retorno de chamada por SMS/MMS número de telefone do remetente Histórico de conversas por remetente
Usuário autenticado ID do usuário ou sujeito do JWT Preferências por usuário
Correlação personalizada ID do pedido, ID do ticket Estado por fluxo de trabalho

Para o cadastro de assinatura — use a Sessão Global

vcr.getGlobalSession() retorna uma sessão determinística compartilhada entre todas as réplicas. Use-a apenas para registrar assinaturas de provedores (voz, mensagens), e não para armazenar o estado do aplicativo.

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 }
);

Resumo

Necessidade Método Âmbito
Contadores compartilhados, caches, configuração vcr.getInstanceState() Todas as réplicas
Estado por usuário/por conversa vcr.createSessionWithId(id) + new State(session) Determinístico, com escopo de solicitação
Dados compartilhados entre aplicativos vcr.getAccountState() Todos os aplicativos da Account
Cadastro de assinatura vcr.getGlobalSession() Singleton válido para toda a instância
Nunca no âmbito global para o Estado vcr.createSession() Aleatório, inacessível por outras réplicas