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 |