5 pontos por GN⁺ 2023-12-02 | 1 comentários | Compartilhar no WhatsApp
  • Apps frontend complexos acabam próximos de reimplementar um pequeno banco de dados em cada projeto, começando com cache de respostas de API e assumindo índices manuais e invalidação de cache
  • Em frameworks declarativos como React, para evitar chamar a API a cada renderização, mantém-se um cache em estado local ou em uma camada como Redux, e essa camada gradualmente passa a atuar como um repositório central
  • Estruturas de consulta por ID e por data tornam as buscas mais rápidas, mas manter consistência entre várias estruturas como CACHE e ENTRIES_BY_DATE aumenta o peso de testes e code review
  • Alterações otimistas atualizam a UI imediatamente antes da resposta do servidor e melhoram a percepção de velocidade, mas trazem um custo de consistência como duplicação de lógica cliente-servidor, rastreamento de mudanças em andamento, rollback de erros e reconciliação após reiniciar o app
  • O SQLSync quer oferecer cache durável, índices, constraints, alterações otimistas e consultas reativas dentro da stack de frontend, com um banco de dados local baseado em SQLite e sincronização semelhante ao Git Rebase

Como o cache de frontend cresce até virar um banco de dados

  • O gerenciamento de dados no frontend pode começar com um simples cache que armazena respostas de API em variáveis locais
    • Frameworks declarativos como React renderizam novamente a árvore várias vezes durante a interação do usuário
    • Para evitar enviar requisições de API a cada renderização, é possível armazenar resultados ou erros da requisição no estado do componente com useState e useEffect
    • O exemplo é simplificado para dar clareza; na prática, também existe a opção de usar bibliotecas de API já consolidadas
  • O cache pode ser movido para camadas mais altas da árvore de UI ou para fora da UI
    • Redux é uma biblioteca de gerenciamento de estado para React que unifica o estado e coordena mudanças atômicas ao longo do tempo
    • O ecossistema do Redux se expandiu com ferramentas e padrões para gerenciar cache de dados de API
    • Esse uso busca centralizar a lógica de cache, coordenar atualizações e compartilhar resultados em cache entre componentes
  • À medida que a camada de cache cresce, ela passa a se parecer mais com um sistema central de armazenamento que lida com dados de forma eficiente para o motor de renderização e para as ações do usuário

Índices manuais e o peso da consistência

  • No frontend também é possível armazenar dados recebidos do servidor em objetos cuja chave é o ID, para consulta e modificação rápidas
    • Em apps que usam REST API, é comum ler dados em lote e depois complementar objetos específicos quando necessário
    • Armazenar objetos por ID facilita mesclar resultados de API ao cache
    • Essa estrutura é otimizada para criar, ler, atualizar e excluir por ID
  • Filtros que precisam percorrer vários itens acabam levando à criação de índices separados para evitar verificar todas as entradas
    • Se for criada uma estrutura ENTRIES_BY_DATE com base em ano/mês/dia de createdAt, fica mais rápido encontrar entradas de uma data específica
    • Em troca, é preciso manter continuamente a consistência entre CACHE e ENTRIES_BY_DATE
    • Consultas por intervalo de datas exigem vários acessos, e uma estrutura melhor pode ser um array ordenado por data com lógica de consulta e atualização mais complexa
  • Quanto mais índices existirem, mais lógica de criação, atualização e consulta será necessária para cada um deles
    • Verificar a correção aumenta o peso de testes e code review
    • Esquecer uma exclusão ou atualização em um índice pode gerar bugs difíceis de encontrar
    • Pode-se gastar mais tempo na infraestrutura para gerenciar essa complexidade do que em novas funcionalidades do aplicativo
  • Índices de bancos de dados reais são muito mais complexos do que simplesmente armazenar os dados em outra forma
    • Entram aí coleta de estatísticas, gerenciamento de versões dos dados, controle transacional, locks e interação com otimização de consultas

Os problemas de consistência adicionados por alterações otimistas

  • Alterações otimistas simulam localmente o efeito de uma ação antes da resposta do servidor
    • A UI pode reagir imediatamente, como se não houvesse latência de rede
    • Se o servidor tomar uma decisão diferente da esperada ou ocorrer um erro, a UI pode precisar reverter a mudança e pedir que o usuário corrija o problema
    • É uma ferramenta poderosa quando o cliente consegue prever bem o resultado do servidor, tratar erros no lado do cliente e manter a lógica fortemente sincronizada
  • Atualizações otimistas normalmente seguem quatro etapas
    • A UI dispara uma operação de escrita
    • A mudança é aplicada ao cache local assumindo que o servidor concordará, e a UI é renderizada novamente de imediato
    • A operação é enviada ao servidor de forma assíncrona
    • A resposta do servidor é mesclada ao cache local para sobrescrever a alteração otimista anterior e, se necessário, renderizar a UI novamente
  • Manter consistência com o servidor traz vários pesos adicionais
    • É preciso duplicar lógica no cliente e no servidor para prever resultados
    • Para lidar com erros assíncronos ou divergências do servidor, é necessário rastrear cada mudança em andamento
    • Para uma experiência melhor, pode ser necessário tornar durável a parte otimista do cache para reconciliar mudanças após reiniciar o app
  • Esse processo aumenta o tempo de desenvolvimento e o custo de verificar a correção, e o gerenciamento de dados pode acabar tomando a frente do trabalho de criar valor ao usuário ou recursos diferenciadores

A complexidade da invalidação recursiva de cache

  • Em apps com muitos dados, a mesma informação aparece em vários pontos do cache
    • O cache do exemplo armazena projects, tasks e users juntos
    • Depois de concluir uma tarefa, várias partes podem ser afetadas, como o progresso do projeto, as tarefas atribuídas a um usuário e informações de novas tarefas
  • Para alinhar o cache com o servidor após concluir uma tarefa, podem ser necessárias várias idas e voltas
    • Informar ao servidor que a tarefa foi concluída
    • Atualizar o projeto, já que o progresso mudou
    • Verificar se houve nova atribuição de tarefa
    • Se houver nova atribuição, buscar essa tarefa
  • APIs mais complexas podem reduzir o número de idas e voltas, mas o resultado continua sendo um acoplamento entre a API ou a lógica do cliente e o modelo de dados subjacente
    • GraphQL é uma abordagem para esse problema, mas não uma solução completa
  • Uma estrutura em que a UI precisa saber quais partes do cache são afetadas por cada mudança se torna frágil conforme cresce
    • Relações e agregações de dados podem afetar várias partes do cache local
    • Quando a equipe de engenharia cresce, o problema pode atravessar fronteiras entre times e passar a lembrar variáveis globais mutáveis em grandes projetos de software
  • Quando combinado com alterações otimistas, o cliente acaba replicando ainda mais lógica de backend para prever mudanças do servidor
    • No exemplo, o cliente pode tentar remover a task 1 do user 1 e recalcular o progresso com uma nova proporção com base no total de tarefas
    • Quanto mais se tenta prever mudanças aninhadas localmente, mais o cliente duplica a stack de backend

A stack de banco de dados para frontend proposta pelo SQLSync

  • SQLSync é uma stack de banco de dados otimizada para frontend construída sobre SQLite
    • O mecanismo de sincronização se baseia em ideias do Git e de sistemas distribuídos
    • Foi projetado para se integrar de forma fluida a frameworks frontend populares como React, Vue e Next.js
    • O objetivo é lidar com problemas difíceis de gerenciamento de dados para que desenvolvedores possam focar nos recursos únicos da aplicação
  • O exemplo de Todo app implementa toda a camada de dados com 60 linhas de Rust e algumas consultas SQL espalhadas pelos componentes
    • O SQLSync oferece cache durável, índices/constraints/triggers/otimização de consultas do SQLite, alterações otimistas, invalidação inteligente de cache e consultas reativas
  • Os dados locais são armazenados em um ou mais bancos SQLite
    • É fácil criar índices, e eles são sincronizados automaticamente com os dados
    • O banco de dados pode usar índices automaticamente para acelerar consultas, como no backend
    • SQL consegue expressar consultas de dados complexas, e também dá para usar recursos como triggers, foreign keys, constraints e full-text search
  • Alterações otimistas são tratadas por um reducer
    • A estrutura é semelhante aos conceitos centrais do Redux
    • O reducer pode ser escrito em qualquer linguagem que possa ser compilada para WebAssembly
    • O SQLSync executa alterações de forma otimista no cliente e, no servidor, as executa em uma ordem globalmente consistente
    • Depois, o cliente sincroniza com o servidor por meio de uma operação semelhante ao Git Rebase
  • Essa arquitetura tem a vantagem de eliminar a necessidade de invalidação recursiva de cache
    • Toda a lógica de mudança de dados é escrita em um reducer que pode ser compartilhado facilmente entre cliente e servidor
    • Todas as mudanças de dados ocorridas durante a alteração ficam visíveis automaticamente
    • Como a sincronização funciona como Git Rebase, mesmo que o servidor faça mudanças diferentes das do cliente, o cliente tem a garantia de chegar ao mesmo resultado consistente

Trabalhos relacionados

  • “Building data-centric apps with a reactive relational database” da Riffle trata da ideia de armazenar todo o estado da aplicação, incluindo estado de UI, em um único banco de dados reativo
    • Consultas reativas oferecem um modelo mental limpo e combinam bem com sistemas declarativos como React
    • Resolve problemas de desenvolvimento de apps cliente com ideias da comunidade de bancos de dados
    • Aborda as vantagens de modelar estado com um modelo de dados relacional e índices reais
  • Stepan, da Instant.db, escreveu dois textos sobre bancos de dados no navegador
    • Database in the Browser, a Spec
    • A Graph-Based Firebase
    • Os dois textos tratam de problemas parecidos com mais foco na relação entre as stacks de frontend e backend, e explicam a motivação para a criação da Instant.db como sucessora baseada em grafos do Firebase
  • O CR-SQLite, de Matt Wonlaw, é uma extensão do SQLite
    • Ele usa tipos de dados replicados sem conflito (CRDT) e logs de eventos com ordem causal para mesclar dados de forma consistente
    • Permite que apps peer-to-peer armazenem dados em SQLite e colaborem sem um coordenador central
    • Também é um exemplo de execução do SQLite no navegador
  • Matt Wonlaw também está explorando ideias relacionadas

1 comentários

 
GN⁺ 2023-12-02
Opiniões do Hacker News
  • Conheço bem este projeto, e como quem o criou é meu amigo, vou tentar trazê-lo aqui para responder perguntas.
    Ele é um arquiteto de banco de dados experiente. Com o SQLsync, ele fez com que desenvolvedores front-end possam consultar e atualizar um banco de dados remoto como se ele estivesse completamente dentro do navegador. Na prática, quase está; graças ao WASM, é possível enviar um banco SQLite inteiro para o navegador. O ponto central está em um algoritmo reativo inteligente, mas simples, que sincroniza vários clientes.
    Se uma parte considerável do trabalho de desenvolvimento é sincronização de dados, React e APIs REST também podem ser vistos como uma espécie de procedimento de sincronização, e esta abordagem abre novas possibilidades. Em vez de criar mais um banco de dados estranho e sob medida com uma árvore de objetos buscada de uma API e mantida em cache, basta atualizar e consultar localmente, diretamente, com o poder de um banco de dados relacional.

    • Acho que levar todo o sistema de consultas para o front-end é o ponto que muitos desenvolvedores front-end realmente querem. Eles querem um sistema de consultas poderoso sobre os dados, não ficar reinventando camada de transporte, REST, GraphQL, *RPC e coisas do tipo.
      Dito isso, em empresas web tradicionais, a adoção é difícil por causa das equipes especializadas de back-end/front-end. É como remover banco de dados, back-end, transporte e camada de autenticação e trocar tudo por um sistema em bloco único; e, como a maioria dos arquitetos de sistemas vem do back-end, eles não entendem bem esse problema. Como mexe profundamente nos dois lados, não se encaixa bem em sistemas existentes e acaba servindo mais para novos desenvolvimentos. O back-end não é um serviço da AWS ou do Azure, nem é amigável a Lambda, então a maioria dos tipos de arquitetos que encontro provavelmente não vai querer tocar nisso.
      Esta abordagem já existia em certa medida com a tecnologia antiga CouchDB+PouchDB. Para alguns usos, ela funciona muito bem, mas o sistema de consultas não é ideal, e a forma de autenticação e de definição do escopo dos dados é estranha para a maioria. O caso mais fácil é quando os dados pertencem totalmente a um único usuário e se usa diretamente um modelo de banco de dados por usuário. Se você particionar fortemente os dados com CRDT, também reduz bastante os problemas de conflito.
      Mas há problemas de escalabilidade. Quando 10 mil a 100 mil usuários se conectam ao CouchDB, a exigência de CPU fica muito alta; a tecnologia também é antiga, embora ainda seja mantida. Do ponto de vista de design de sistemas, quando você começa a compartilhar dados entre usuários, a complexidade cresce rapidamente; em vez de resolver a complexidade, você apenas a desloca, o que reduz a adequação da solução.
      Esta abordagem também parece mirar o mesmo objetivo, mas provavelmente sofrerá problemas de escalabilidade semelhantes. Estou curioso para ver como ela vai evoluir; parece um primeiro passo.
    • Isso soa bem parecido com o Couchbase, que permite consultar/atualizar um banco de dados sincronizado remotamente e depois sincronizado novamente para peers. Também dá para controlar facilmente autenticação ou lógica de negócio com plug-ins JavaScript do lado do servidor.
    • Tenho uma curiosidade genuína: por que não fazer cache apenas das partes relevantes em LocalStorage / SessionStorage?
      Lembro que, no passado, o Chrome tentou literalmente colocar um banco de dados SQL no navegador, mas isso não deu muito certo, e o localStorage acabou virando o padrão. Não estou tentando diminuir a utilidade; é só que normalmente escolho o que o navegador oferece. Tenho grandes expectativas quanto à possibilidade de trazer mais coisas para o navegador com WASM, à medida que ele amadurece ou ganha mais recursos.
  • Uma empresa em que trabalhei no passado usava um software de gerenciamento de projetos com mecanismo de check-out/check-in para alterações. Ao fazer check-out de um projeto, você baixava uma cópia para editar localmente; ao fazer check-in, enviava de volta para o servidor. Enquanto estava em check-out, o projeto ficava bloqueado. Na era dos apps com atualizações ao vivo, todo mundo achava aquilo ultrapassado.
    Mas, depois de 10 anos criando webapps SPA, essa forma de sincronizar dados agora parece à frente do seu tempo.

    • Dar suporte a atualizações ao vivo paralelas de vários usuários, ou bloquear para que apenas uma atualização aconteça por vez, no fim das contas não é uma decisão técnica, mas de negócio. O ponto central é se as regras de negócio adequadas à aplicação permitem lidar com atualizações ao vivo simultâneas.
      No fim, tudo se resume a conseguir implementar um procedimento que resolva de forma consistente as divergências entre várias atualizações simultâneas. Em alguns casos isso é possível, em outros não; e isso depende mais das regras de negócio do que da capacidade técnica.
      Se, pelas regras de negócio, não for possível implementar um mecanismo de resolução, então, mesmo que haja capacidade técnica para suportar atualizações simultâneas, é necessário um bloqueio para permitir apenas uma atualização por vez.
    • Seguindo por esse caminho, muitos problemas são resolvidos, e a implementação fica muito fácil.
      Mas é difícil convencer as pessoas de que isso é o que elas realmente querem. É fácil cair na grande ilusão de que tudo precisa estar sempre disponível, mas, na realidade, normalmente uma pessoa faz uma alteração por vez; e, quando duas ou mais pessoas precisam trabalhar, muitas vezes elas já terão de conversar ou se comunicar para se alinhar.
      Mesmo no desenvolvimento totalmente distribuído como no Git, não dá para resolver conflitos automaticamente por mágica. Para escolher a alteração correta, ainda é preciso se comunicar com outras pessoas e entender o contexto.
    • É justamente por isso que o paradigma clássico de bancos de dados relacionais como MySQL continua sobrevivendo, mesmo quando as pessoas o tratam como inferior a bancos não relacionais como NoSQL ou MongoDB. Não dá para substituir tudo só porque algo é mais rápido ou mais moderno.
      Algumas coisas precisam de soluções comprovadas.
    • Soa como RCS https://en.wikipedia.org/wiki/Revision_Control_System
      Lembro que, quando a empresa mudou de RCS para CVS, um dos meus colegas ficou irritado porque o CVS não dava suporte a check-out com bloqueio.
      https://en.wikipedia.org/wiki/Concurrent_Versions_System
    • Gosto desta abordagem. O SQLSync, na prática, faz isso continuamente, mas, com coordenação explícita, esse tipo de check-in/check-out também deve ser possível.
      Acho que também dá para simular uma estratégia de bloqueio com proprietário único usando o SQLSync. Dependendo do app, porém, talvez isso nem seja necessário. Se o objetivo é fazer trabalho offline e depois mesclar quando estiver pronto, o SQLSync oferece esse padrão de forma nativa. Se o objetivo é permitir que apenas um cliente faça alterações, será necessário um padrão de bloqueio central; e é provável que isso também possa ser coordenado por meio do SQLSync.
  • Aqui entram em jogo o princípio de que “o que é medido é gerenciado” e a falácia do custo irrecuperável
    O verdadeiro problema dos bancos de dados é a complexidade. Cada recurso isolado em geral é seguro, mas, quando estabilidade, cache e índices começam a interagir, a complexidade explode, e normalmente não faz sentido implementar um banco de dados específico de domínio
    Só que, quando a empresa percebe que já investiu e dedicou muitos recursos à implementação desses três recursos, fica politicamente difícil recomendar remover tudo, e o custo real de eliminar a dívida técnica de uma vez também é alto
    Vejo o problema real como sendo a sintaxe SQL. Se a experiência de usar um banco de dados relacional básico fosse tão agradável quanto uma sintaxe familiar no estilo C, em vez de um inglês quebrado, haveria um incentivo maior para usar um DB em vez de criar um por conta própria. Bancos de dados NoSQL foram um bom passo nessa direção, mas em geral se concentraram demais em big data, mais do que na utilidade cotidiana. Algo como Redis se consolidou e é bom
    Tornar SQL fácil de executar é uma abordagem razoável, mas bons bancos de dados, por exemplo o Postgres, de que gosto, têm SQL como linguagem padrão, então é difícil obter eficiência sem usar essa linguagem. Precisamos muito de um banco de dados como PostgresPostSQL, que replique perfeitamente o Postgres, mas cujo parser padrão suporte uma linguagem com uma boa sintaxe

    • Não sei exatamente o que há de difícil em SQL. Acho que todo desenvolvedor deveria saber SQL. A sintaxe SQL também é boa e foi comprovada o suficiente para sobreviver por muito tempo. Em vez de criticá-la, seria mais útil investir tempo em realmente aprendê-la
    • SQL é criticado com frequência, e eu também acho que há motivos para isso, mas por que não conseguimos criar algo melhor?
      Na programação em geral, dezenas de linguagens são usadas e continuam evoluindo. Até JavaScript, que é difícil de mudar porque roda no navegador e não podemos controlar o navegador do usuário, está evoluindo por meio de transpilers e WebAssembly
      Mas, em bancos de dados, na prática só existe um SQL. Há alternativas, mas nenhuma chega perto de SQL em termos de uso. Talvez SQL não seja tão ruim assim
      O motivo pode ser que o modelo relacional é realmente muito bom. Tentativas de se afastar dele provavelmente só funcionam em nichos. O estilo declarativo também é muito bom, e é difícil ter grande sucesso ao se afastar dele. No fim, se você acaba criando apenas um SQL com sintaxe diferente, para a maioria das pessoas isso não será uma melhoria grande o bastante para justificar mudar de abordagem
    • Gostaria que o Postgres tivesse uma API mais estável e de nível mais baixo do que SQL. Talvez fosse algo parecido com o plano de consulta obtido com EXPLAIN
      Uma aplicação escrita para essa API poderia implementar um DB SQL. Bastaria fazer o parsing de SQL e implementar um planejador de consultas que emitisse planos de consulta compatíveis com essa API
    • Fico curioso se você já analisou a abordagem de adicionar uma camada semântica sobre o banco de dados para pessoas que querem evitar escrever SQL diretamente
  • Sou o autor. Mal consegui passar os olhos pela maioria das perguntas, e vou continuar verificando periodicamente se deixei alguma passar. Também fico curioso para saber se alguém já criou uma forma melhor de acompanhar discussões no HN
    Fico muito feliz com a discussão até agora. O primeiro texto se concentrou mais nas motivações de engenharia de frontend que me levaram a criar o SQLSync do que em como o SQLSync funciona especificamente. Pretendo abordar o funcionamento no próximo texto

    • Fico curioso para saber se há planos de suporte a mobile. É exatamente em mobile que eu mais gostaria de testar isso
  • Não se deve dar ao usuário um modelo mental que a realidade possa quebrar de forma grave ou invisível
    Tenho receio de que sincronizar bancos de dados em vez de usar o modelo cliente-servidor seja um desses casos. O mecanismo de sincronização pode simplesmente derreter, ou pode haver pressupostos profundos não satisfeitos
    Se for preciso uma UI rápida, parece mais seguro criar e usar um conjunto de primitivas CRDT, e o resto provavelmente ficaria restrito a envio de formulários

    • Concordo. Um dos objetivos é fazer com que os desenvolvedores que usam SQLSync consigam entender facilmente o modelo mental. Posso estar enviesado, mas pessoalmente sinto que o modelo de rebase é muito mais fácil de entender do que CRDT
  • Sincronizar estado entre cliente e servidor é um problema amaldiçoado
    Se você aceitar sacrificar um pouco da experiência do usuário e voltar para algo mais próximo do modelo PHP/renderização do lado do servidor, pode evitar esse problema por completo. SPAs são boas, mas envio de formulário multipart ainda funciona. Com pouquíssimo JavaScript, dá para aparar a maioria das arestas restantes
    Nos produtos web recentes, o estado do lado do cliente é apenas as claims de autenticação do IdP de terceiros, o ID de sessão first-party nos parâmetros de consulta e o documento atual. Para ser sincero, nem eu sei onde o primeiro é armazenado. Isso é problema da Microsoft, não nosso. Todo o restante do estado fica no servidor
    Tratamos o cliente como um terminal burro que passa o dia inteiro apenas emitindo entradas. Não usamos cookies first-party nem armazenamento local. Essa abordagem melhorou muito a experiência de desenvolvimento para iOS/Safari
    Então gostaria de perguntar qual é a experiência que você realmente quer oferecer e por que ela justifica separar o estado do cliente e do servidor

    • Em interfaces gráficas para consumidores, renderização otimista é um fluxo muito comum, e, se você faz isso, está lidando com estado cliente/servidor. Ainda se veem spinners de carregamento aqui e ali, mas geralmente para o carregamento inicial de conteúdo. Por exemplo, o Gmail não faz você esperar quando arquiva um e-mail
    • O ElectricSQL fez grandes avanços para resolver esse problema. Ele permite escrever no SQLite no cliente e garante a sincronização com o Postgres
      Referência: https://news.ycombinator.com/item?id=37584049
  • Parece que offline/local-first baseado em SQLite está em alta hoje em dia. É a terceira vez que leio sobre isso esta semana, e parece promissor
    Mas como isso se compara ao ElectricSQLhttps://electric-sql.com/ e ao PowerSynchttps://powersync.com/?

    • É realmente uma área em alta. É muito interessante ver abordagens diferentes surgindo
      Tanto o ElectricSQL quanto o PowerSync lidam com o problema muito difícil da replicação parcial. A ideia é criar uma solução geral em que um banco de dados central tradicional sincronize em mão dupla apenas o que o cliente precisa, ao mesmo tempo que oferece suporte a alterações otimistas e ao consequente tratamento de consistência/conflitos
      A desvantagem é a complexidade de implementação. É preciso rastrear com precisão qual subconjunto do banco de dados inteiro cada cliente possui, para poder enviar alterações apenas para esse subconjunto. Além disso, para especificar qual subconjunto do estado do banco de dados será baixado, é necessária uma nova DSL, que também precisa ser aprendida e otimizada. Ainda assim, é ótimo ver que eles estão resolvendo um problema muito difícil, e quando o SQLSync estiver pronto para dar suporte à replicação parcial, as melhores práticas já estarão consolidadas
      Por outro lado, o SQLSync atualmente só oferece suporte à sincronização do DB inteiro. Todos os clientes veem uma visão consistente do banco de dados completo. Isso pode levantar de imediato a dúvida se é uma boa ideia, e não serve para alguns aplicativos. Mas, se pensarmos em um app de finanças pessoais, os objetivos principais são sincronização entre dispositivos, backup na nuvem e disponibilidade offline, então ter o DB completo armazenado em todos os dispositivos pode ser exatamente o comportamento desejado. Um modelo de dados orientado a documentos, como o do Airtable, também pode ser um exemplo. Se cada Airtable for tratado como um banco de dados separado, o cliente pode gerenciar quais tabelas importam
      Ao focar na sincronização do DB inteiro, o mecanismo de sincronização fica muito mais simples do que em soluções que dão suporte à replicação parcial. Uma das vantagens disso é que o backend é muito leve. A demo atual (https://sqlsync-todo.pages.dev) roda completamente dentro de Cloudflare Durable Objects, usando pouquíssimo armazenamento e tempo de CPU
      Ainda há muito trabalho a fazer para que o SQLSync viabilize esses casos de uso, e ele ainda está mais para um protótipo, mas os testes iniciais foram muito positivos
  • Em grandes apps multitenant, quando os datasets individuais são relativamente pequenos, já pensei várias vezes: “e se simplesmente mandássemos o banco de dados para o cliente?”. Não fui muito a fundo porque parecia um padrão de arquitetura amaldiçoado, suficientemente fora do padrão. Seria bom descobrir que eu estava errado

    • Já fiz isso uma vez em um app de contagem de calorias. Mesmo com centenas de milhares de alimentos no banco de dados, o app ocupava muito menos espaço do que a maioria dos apps de mídia ou jogos
    • Também fico me perguntando se esse é um jeito amaldiçoado de fazer as coisas. Até agora, tem sido muito melhor do que eu esperava. Apps como https://sqlsync-todo.pages.dev ficam triviais com esse padrão
      Ainda há muito a provar de verdade, mas estou bem animado para continuar insistindo nisso e ver aonde leva
    • A resposta correta é levar a UI de volta para o servidor onde o banco de dados já está e enviar apenas HTML para o renderizador HTML do lado do cliente, isto é, o navegador web. Este texto inteiro está mais para “o frontend passou dos limites, cruzou o precipício e caiu no mar”
  • Isso parece um daqueles problemas que desaparecem completamente quando se abandona a SPA
    Com soluções da família Hotwire ou htmx, as consultas viram simplesmente consultas no servidor, e o problema de torná-las rápidas é muito mais bem compreendido

    • Isso não é um problema apenas de sites. Os ecossistemas mobile e desktop também deveriam se mover fortemente para thin clients, como o navegador? Apps simples como Apple Reminders ou Google Tasks deveriam travar a GUI por causa de latência ou problemas de conexão?
    • Usei htmx recentemente, e a complexidade que ele elimina, junto com a produtividade resultante, é realmente absurda. Poder manter a stack que você quiser também é uma bênção enorme
      Usei com ocaml + web components, e foi uma experiência de produtividade 10/10. Você só precisa de uma ferramenta de build que compile mais rápido do que um piscar de olhos, e não há necessidade de fiação de mapeamento JSON entre frontend e backend, então é realmente produtivo
    • Sinceramente, soluções como Hotwire ou Livewire não são tão imediatas quanto uma SPA
      Pessoalmente, prefiro InertiaJs https://inertiajs.com. É uma espécie de sistema de roteador frontend que sincroniza estado com o servidor “à moda antiga”
    • É parecido com dizer “não crie webapps com muita interação” ou “não opere um serviço que precise de mais de uma VM”. É uma opinião absurda
    • Acho que o problema desaparece não quando se abandona a SPA, mas quando se abandona os recursos de alta interação que produtos bons costumam ter
      Especialmente se for um produto que precisa funcionar em regiões com internet instável
  • Agora estou escrevendo um texto muito parecido sobre “bancos de dados full-stack”. Ele trata do padrão em que muitos apps recriam a lógica do backend e do banco de dados no código do cliente frontend. A solução que recomendamos é escolher um banco de dados que possa rodar tanto no servidor quanto no cliente e sincronizar os dois
    O motivo de não usarmos SQLite no nosso produto é que, sinceramente, SQL não é a ferramenta adequada para consultar dados de aplicações. Ele não se encaixa facilmente nas estruturas de dados desejadas no código do cliente, e quase todos os bancos de dados SQL não têm uma forma de assinar mudanças em consultas sem fazer polling repetido delas
    Se você gosta da ideia de ter um banco de dados completo no cliente e quer uma integração profunda com TypeScript/JavaScript, dê uma olhada no https://github.com/aspen-cloud/triplit que estamos criando

    • Na verdade, o Postgres oferece uma ótima forma de fazer assinatura de mudanças em tempo real via WAL. Eu também mantenho esta biblioteca open source
      https://github.com/cpursley/walex
    • O SQLite de fato fornece, por meio do update hook, o mecanismo necessário para detectar mudanças. É uma pena que muitos bindings do SQLite não o exponham
      Eu uso de uma forma bem simples. Quando os dados nas tabelas subjacentes mudam, a consulta é executada novamente automaticamente. Pode não ser tão eficiente quanto atualizar os resultados de forma incremental, mas, como as consultas SQLite geralmente são muito rápidas, não vejo isso como um grande problema
    • Não concordo que ele não se encaixe facilmente nas estruturas de dados desejadas no código do cliente. Normalização de dados é importante para aplicações frontend reativas e, na prática, necessária para manter os dados atualizados. Todas as operações CRUD também ficam muito mais fáceis de lidar
    • Com Realm Sync e Mongo + Kotlin Multiplatform, dá para cobrir praticamente todas as plataformas, como servidor, web, mobile e desktop. Claro, isso tem um custo. Tenho interesse real em alternativas e fico curioso para saber se esse conteúdo também vai entrar no texto