Início/Blog/Sistemas offline-first
Arquitetura de Aplicações Resilientes

A lacuna de uso de 32%: sistemas offline-first para falhas de internet móvel

A GSMA reporta uma usage gap móvel de 32% na América Latina: pessoas vivem dentro da cobertura, mas não usam internet móvel. Software não resolve sozinho preço, habilidades, confiança ou desigualdade, mas pode parar de presumir conexão perfeita, dados ilimitados e aparelhos premium.

Usage gap não é a mesma coisa que falha de cobertura

A GSMA separa coverage gap de usage gap. Uma pessoa coberta ainda pode enfrentar aparelho ou plano caro, baixa alfabetização digital, insegurança, falta de conteúdo local, hardware compartilhado ou experiência instável. Arquitetura offline-first trata apenas parte disso: reduz transferência, espera, trabalho perdido e dependência de sessões contínuas.

O objetivo não é apenas “o app abre sem internet”. O usuário deve entender os dados disponíveis, concluir trabalho crítico localmente, ver o que está pendente, reconectar sem duplicar efeitos, resolver conflitos e confiar que nenhuma ação desapareceu.

Budget de dadosInstalação, payload por rota, mídia, tráfego em background, batch de sync e consumo mensal.
Budget de tempoPrimeira tela útil, confirmação local, atraso máximo de sync, deadline de retry e conflito.
Budget de storageAssets, registros, anexos, outbox, logs, criptografia e política de eviction.
Budget de confiançaO que pode ser lido ou alterado offline, token, sensibilidade, reautenticação e auditoria.

O banco local deve dirigir a interface

O guia offline-first do Android recomenda uma fonte local canônica para leituras. O mesmo vale na web com IndexedDB ou outro banco durável. A UI observa o estado local; respostas da rede atualizam esse estado; a tela não alterna entre modelos separados de cache e servidor.

Use Cache API e service workers para assets versionados e estratégias de request. Use IndexedDB ou banco embarcado para dados estruturados, índices, operações e anexos. Um shell HTML em cache não é uma aplicação sincronizada, e localStorage não é banco transacional.

Represente writes como operações duráveis

Ao enviar formulário, inspeção, tarefa ou evidência, grave a mudança e uma operação de outbox na mesma transação local. A UI mostra “salvo neste aparelho”, mantendo “enviado à organização” como estado separado.

Cada operação precisa de ID estável, entidade, ator, device, sequência local, versão-base, schema, horário, dependências, payload, tentativas, próximo retry, expiração e classe de policy. O servidor aceita idempotency key para impedir pedidos, visitas, tickets ou pagamentos duplicados.

Máquina de estados da sincronização
01 Local prontoLeia sem redeRenderize snapshot local com frescor, permissões, storage e indicadores de dados ausentes.
02 Commit localSalve atomicamenteGrave mudança e outbox juntas; informe que a ação está armazenada no device.
03 Na filaAguarde sync elegívelRespeite conexão, bateria, rede tarifada, tamanho, escolha do usuário, dependências e deadline.
04 EnviandoPush idempotenteAutentique, envie batch limitado, inclua versões-base e capture resultado por operação.
05 AceitoAplique o ACKArmazene IDs, versões, timestamps e campos canônicos; remova apenas entradas reconhecidas.
06 ConflitoExecute policy de domínioFaça merge seguro, preserve revisões, peça decisão ou rejeite ação stale consequente.
07 Retry ou bloqueioClassifique a falhaBackoff para transientes; pare em auth, validação, quota, policy, expiração ou falha permanente.
08 ReconciliadoPuxe mudanças incrementaisUse cursors e tombstones, atualize o local, exponha outcomes e retenha auditoria mínima.

Sincronização é protocolo, não loop de retry

Defina push e pull, ordenação, paginação, cursors, versões, deleções, anexos, sucesso parcial, expiração de auth, migração de schema e compatibilidade. A resposta deve classificar cada operação: aceita, duplicada, conflito, inválida, não autorizada, expirada, limitada ou temporariamente indisponível.

Puxe mudanças por cursor estável. Mantenha tombstones para devices que retornam depois de semanas. Não confie em relógios de aparelhos baratos; use versão do servidor, sequência lógica ou causalidade quando a ordem importa.

Política de conflito pertence ao domínio

TipoEstratégia útilExemploAtalho inseguro
Evento append-onlyPreservar eventos, deduplicar por operation ID e ordenar por sequência de domínio.Inspeção, nota de campo, scan de entrega, telemetria.Sobrescrever porque o timestamp local é mais recente.
Campos independentesMerge por campo com proveniência e validação.Nota de endereço e preferência telefônica alteradas separadamente.Last-write-wins para o registro inteiro.
Texto ou coleção colaborativaCRDT ou merge por operações quando a semântica justificar.Notas, checklist e anotações compartilhadas.Usar CRDT em estoque, dinheiro ou autorização sem regra de domínio.
Estoque ou capacidade finitaReserva server-authoritative, conditional write, fila ou compensação.Estoque, horário, assento ou crédito.Aceitar todo decremento offline e gerar quantidade negativa.
Transição de workflowValidar estado/versão esperados e rejeitar ou transformar explicitamente.Fechar ticket já transferido ou cancelado.Reproduzir cegamente a ordem do device.
Pagamento ou segurançaExigir autorização atual, limites online, idempotência, expiração e decisão do servidor.Transferência, reset, grant ou aprovação de alto risco.Executar automaticamente uma ação sensível dias depois.

Repita apenas falhas que podem melhorar

Use timeout, backoff exponencial, jitter, limite e deadline. Evite tempestades quando milhares de aparelhos reconectam. Faça batch com limite de bytes e itens, preservando resultado individual.

Não repita validação, credencial revogada, ação proibida, operação expirada, schema incompatível ou conflito aberto. Mostre trabalho bloqueado e como reparar. “Ainda sincronizando” não pode ser estado permanente.

UX de baixo consumo começa no payload

Defina budgets de bytes. Entregue shell e dados críticos, pagine histórico, compacte schemas, comprima, peça deltas, redimensione imagens antes do upload, separe thumbnail de original e torne mídia explícita em rede tarifada. Cancele requests superados.

Mostre conteúdo local imediatamente. Diferencie dados frescos, stale, ausentes, modificados, queued, syncing, em conflito, rejeitados e sincronizados. Prefira “3 inspeções salvas neste aparelho; último sync há 2 dias” a um indicador verde ambíguo.

Background sync é otimização, não garantia

Service workers podem cachear assets e interceptar requests. Background Sync pode pedir retry ao voltar a conexão, mas suporte e scheduling variam; periodic sync ainda é experimental em alguns ambientes. Sincronize também no launch, resume, ação explícita e foreground.

Jobs nativos enfrentam limites de bateria, dados e sistema operacional. A outbox é a dona do trabalho; o scheduler apenas dispara tentativas.

Autenticação offline tem limites estritos

Autorização em cache é uma decisão de risco. Permita leitura ou draft de baixo risco por período limitado, mas não trate token antigo como permissão para ação crítica. Minimize dados, use criptografia da plataforma, separe usuários e limpe material no logout ou wipe.

Exija autorização online atual para pagamentos, privilégios, recuperação, comandos destrutivos ou policy mutável. Sem verificar policy, preserve um draft em vez de prometer execução.

Teste a máquina de estados

Teste rede lenta e com perda, DNS, captive portal, timeout após commit do servidor, duplicata, batch parcial, reconnect storm, token expirado, clock skew, schema upgrade, registro deletado, storage cheio, eviction, processo morto, update, device compartilhado e conflitos após ausência longa.

Use testes determinísticos de merge e transições, property tests de ordem e duplicação, integração entre versões e testes de device com network shaping. Um screenshot em airplane mode prova pouco.

Observe sync como workflow de produto

Meça latência local, profundidade e idade da outbox, bytes, sucesso, retries, bloqueios, conflitos, ACKs duplicados, cursor lag, storage, eviction, idade dos dados, execução de jobs e retries manuais. Segmente por versão, device, país, operadora, conexão e workflow.

Outcomes são tarefas concluídas, trabalho recuperado, abandono, suporte, custo de dados e bateria e tempo até confirmação organizacional. 99,9% de sucesso técnico pode esconder as operações mais antigas e importantes.

O que eu construiria

Eu construiria uma camada offline reutilizável com banco relacional local, repositories observáveis, outbox transacional, protocolo versionado, anexos, scheduler limitado, handlers de conflito, hooks de criptografia, migração e dashboard operacional.

Cada módulo declararia o que pode ser lido e escrito offline, idade máxima, merge, autorização online, budgets e estados de UX. Assim, offline não fica espalhado em handlers de erro de rede.

O princípio de design

Offline-first não é fingir que o servidor não existe. É manter o trabalho local coerente quando a rede desaparece e tratar sincronização como protocolo distribuído com estado visível, conflitos de domínio, risco limitado e evidência de que nenhuma ação sumiu.