Um aplicativo mobile não precisa estar completamente offline para se tornar praticamente inutilizável.
Às vezes o dispositivo está tecnicamente conectado à internet, mas a conexão está extremamente lenta, instável ou sofrendo perdas constantes.
E é aí que começam os problemas:
- requisições entram em timeout;
- telas ficam carregando indefinidamente;
- usuários tentam executar a mesma ação várias vezes;
- operações podem ser duplicadas;
- o aplicativo não sabe se deve tentar novamente, esperar ou simplesmente falhar;
- e, no final, o usuário fecha o app.
Grande parte das bibliotecas de conectividade responde muito bem a uma pergunta:
O dispositivo está online ou offline?
Mas, em aplicações mobile reais, isso nem sempre é suficiente.
Foi pensando nisso que comecei a desenvolver o NetResilience SDK.
O que é o NetResilience?
O NetResilience é um SDK open source para React Native focado em tornar a comunicação HTTP mais resiliente em condições de internet ruim.
Em vez de tratar a conectividade apenas como online ou offline, o SDK tenta entender a qualidade real da conexão.
Atualmente, a rede pode ser classificada como:
EXCELLENT
MODERATE
POOR
OFFLINE
A partir disso, a aplicação pode decidir como cada requisição deve se comportar.
Uma requisição pode:
- ser executada imediatamente;
- ser armazenada localmente;
- entrar em uma fila;
- ser tentada novamente;
- aguardar uma conexão melhor;
- ou ser rejeitada conforme sua política de resiliência.
A ideia básica
Imagine que um usuário esteja criando um pedido enquanto utiliza uma conexão móvel instável.
Normalmente teríamos algo parecido com:
await api.post("/orders", order);
Se a conexão falhar no momento errado, a aplicação precisa decidir o que fazer.
Com o NetResilience, esse comportamento pode ser definido explicitamente.
const result = await net.post("/orders", order, {
resilience: {
queue: true,
queueOnNetworkError: true,
safety: "IDEMPOTENCY_PROTECTED",
priority: "HIGH",
},
});
Se a requisição não puder ser enviada com segurança naquele momento, ela pode ser persistida no dispositivo.
if (result.status === "queued") {
showMessage(
"Pedido salvo no dispositivo e aguardando conexão."
);
}
Quando a conectividade melhorar, o SDK pode tentar sincronizar automaticamente as operações pendentes.
Fila persistente de requisições
Uma das partes mais importantes do projeto é a fila de requisições.
O NetResilience possui suporte a armazenamento persistente utilizando expo-sqlite.
import * as SQLite from "expo-sqlite";
import {
ExpoSQLiteQueueStorage,
NetResilienceClient,
} from "@netresilience/react-native";
const db = await SQLite.openDatabaseAsync(
"netresilience.db"
);
const net = new NetResilienceClient({
baseUrl: "https://api.example.com",
storage: new ExpoSQLiteQueueStorage(db),
authProvider: async () => {
return auth.getCurrentToken();
},
});
await net.initialize();
Isso permite que operações pendentes sobrevivam mesmo se o aplicativo for fechado ou reiniciado.
Ou seja, a fila não existe apenas em memória.
Autenticação também virou um problema interessante
Quando começamos a persistir requisições HTTP localmente, aparece uma preocupação importante:
segurança.
Não é uma boa ideia armazenar tokens de autenticação diretamente junto com as requisições pendentes.
Por isso, o NetResilience remove informações sensíveis antes de persistir as operações.
No momento da sincronização, o SDK solicita novamente um token atualizado através do authProvider.
authProvider: async () => auth.getCurrentToken()
Isso também evita um problema comum:
uma requisição ficar horas na fila e depois tentar ser enviada utilizando um token expirado.
Estratégia de retry
Tentar novamente imediatamente várias vezes não é exatamente resiliência.
Na prática, isso pode apenas aumentar o problema.
Imagine centenas ou milhares de dispositivos tentando atingir uma API repetidamente durante uma instabilidade.
Por isso, o SDK utiliza uma estratégia de:
exponential backoff + jitter
De forma simplificada:
requisição falha
↓
espera
retry
↓
espera mais tempo
retry
↓
conexão melhora
sincronização acontece
O jitter adiciona pequenas variações ao tempo de espera.
Isso ajuda a evitar que milhares de clientes tentem novamente exatamente no mesmo momento.
Esse problema é conhecido como:
Thundering Herd
Idempotência é extremamente importante
Retries podem se tornar perigosos quando estamos lidando com operações como:
- pagamentos;
- criação de pedidos;
- reservas;
- transferências;
- alteração de saldo.
Imagine o seguinte cenário:
POST /payments
↓
servidor processa o pagamento
↓
conexão cai antes da resposta chegar ao aplicativo
↓
aplicativo entende que ocorreu erro
↓
requisição é enviada novamente
Resultado:
o pagamento pode ser processado duas vezes.
Por isso, o NetResilience permite marcar determinadas operações como:
safety: "IDEMPOTENCY_PROTECTED"
Nessas operações, o SDK pode gerar e reutilizar uma:
Idempotency-Key
durante os retries.
Mas existe uma regra importante:
O backend também precisa implementar idempotência.
O SDK não consegue transformar automaticamente uma API não idempotente em uma API segura.
Qualidade da conexão
Outro objetivo do projeto é não depender exclusivamente da informação fornecida pelo sistema operacional.
Ter Wi-Fi conectado não significa necessariamente ter uma boa conexão com a API.
Por isso, o SDK pode aprender com o tráfego HTTP real da aplicação.
Algumas informações podem ser observadas, como:
- latência;
- falhas;
- timeouts;
- quantidade de bytes transferidos;
- estabilidade das requisições.
Também é possível realizar probes HTTP opcionais.
net.startProbing({
url: "https://probe.example.com/health",
stableIntervalMs: 60_000,
poorIntervalMs: 15_000,
});
A ideia é utilizar essas informações para construir uma visão mais realista da conectividade do dispositivo.
Nem toda requisição deve entrar em uma fila
Esse foi outro ponto importante durante o desenvolvimento.
Não faria sentido simplesmente armazenar qualquer requisição que falhasse.
Por exemplo:
GET /restaurants
Provavelmente não precisa ser executada horas depois.
Mas algo como:
POST /orders
pode precisar.
Por isso, o comportamento é definido por requisição.
resilience: {
queue: true,
queueOnNetworkError: true,
priority: "HIGH"
}
Isso permite que a aplicação tenha políticas diferentes dependendo da importância da operação.
Onde esse tipo de SDK pode ser útil?
A ideia do NetResilience não é simplesmente detectar se existe internet.
O objetivo é criar uma camada entre:
Aplicação
↓
NetResilience
↓
HTTP
↓
Backend
capaz de tomar decisões melhores quando a rede estiver instável.
Esse tipo de abordagem pode ser útil principalmente em aplicações como:
- mobilidade urbana;
- delivery;
- logística;
- sistemas de campo;
- aplicativos financeiros;
- sistemas utilizados em regiões com cobertura móvel limitada;
- aplicativos que precisam continuar funcionando mesmo com internet instável.
Em muitos desses cenários, o problema não é estar totalmente offline.
O problema é ter uma conexão que existe, mas que não é confiável.
Por que resolvi tornar o projeto open source?
O NetResilience ainda está em desenvolvimento.
Algumas decisões de arquitetura provavelmente vão evoluir conforme novos cenários forem testados.
E foi justamente por isso que resolvi deixar o projeto open source.
Acredito que esse tipo de problema pode ser melhor resolvido com a participação de outros desenvolvedores.
Existem vários cenários que uma única pessoa dificilmente consegue testar sozinha:
- diferentes dispositivos;
- diferentes versões do Android e iOS;
- conexões móveis instáveis;
- redes congestionadas;
- APIs com comportamentos diferentes;
- aplicações com milhares de requisições;
- diferentes estratégias de sincronização.
Quanto mais cenários reais forem testados, mais robusto o SDK pode se tornar.
Quer contribuir?
Se você trabalha com React Native, redes, sistemas distribuídos, backend ou simplesmente achou a ideia interessante, contribuições são muito bem-vindas.
Você pode ajudar de várias formas:
- sugerindo melhorias;
- abrindo Issues;
- reportando bugs;
- propondo novas funcionalidades;
- melhorando a documentação;
- criando testes;
- analisando a arquitetura;
- enviando Pull Requests;
- compartilhando experiências reais com conexões instáveis.
Também tenho interesse em receber feedback principalmente sobre:
- arquitetura da fila;
- estratégias de retry;
- detecção da qualidade da rede;
- idempotência;
- sincronização;
- performance;
- experiência da API do SDK.
Repositório
O projeto está disponível no GitHub:
https://github.com/rafaelgoesti/netresilience-sdk
Se quiser contribuir com o desenvolvimento, fique à vontade para abrir uma Issue ou enviar um Pull Request.
E se você gostar da ideia, deixar uma ⭐ no repositório também ajuda bastante a dar visibilidade ao projeto.
A ideia é fazer o NetResilience crescer como um projeto realmente construído com a comunidade.
🚀
Top comments (0)