Ir para o conteúdo

Guias de engenharia

Como criar um importador de CSV para uma aplicação SaaS

Um guia de engenharia sobre parsing, mapeamento, autoridade sobre o tenant, validação, gravações, novas tentativas e reconciliação, com um caminho claro para importações mais simples.

Mohamed Atef · ImportFlow
Última revisão

Crie o fluxo mais simples que permita ao usuário revisar uma proposta de importação e ao backend prestar contas de cada registro aceito. Para um recurso reutilizável, isso geralmente significa recebimento de arquivos com limites, parsing, um mapeamento de colunas aprovado, validação e correção, um plano de gravação confirmado e um resultado persistente. O componente que grava no destino deve aplicar a autorização e o comportamento de novas tentativas independentemente do navegador.

Para um único arquivo conhecido, um script revisado ou o Editor de tabelas do Supabase pode bastar. Você não precisa criar um produto de importação para migrar um cliente. Este guia mostra em quais pontos a complexidade de um recurso reutilizável se justifica e apresenta decisões concretas que um engenheiro precisa tomar em cada fronteira.

Escopo e premissas. Orientações de arquitetura para importações de dados de clientes no PostgreSQL/Supabase. A semântica SQL segue o PostgreSQL 17. O pseudocódigo descreve um projeto, não um SDK do ImportFlow.

Decida se você precisa de um recurso reutilizável

NecessidadeComece comMotivo para expandir
Um conjunto de dados interno, esquema estável e operador técnicoImportação de CSV integrada ou um pequeno script revisadoAs exigências de repetibilidade ou auditoria superam o que o script oferece
Uma migração de cliente com significados de campos diferentesMapeamento explícito, ensaio isolado e execução revisadaAs mesmas decisões se repetem entre clientes
Clientes enviam arquivos sem a presença de um engenheiroUm fluxo de importação com correção e resultados persistentesO suporte precisa resolver falhas sem reproduzir uma sessão do navegador

Uma ferramenta interna de upload pode pressupor um operador treinado e um formato de arquivo controlado. Na integração de clientes, geralmente não é possível fazer essa suposição. O cliente pode não saber qual aba é a fonte oficial, por que um valor de enum foi rejeitado ou quais registros já existem. A interface precisa tornar essas decisões visíveis sem exigir que o cliente entenda seu esquema.

Parsers existentes e interfaces de importação de código aberto podem poupar um trabalho útil. Avalie seu comportamento com os casos reais de arquivo e leia em que ponto a responsabilidade volta para sua aplicação. Um callback com linhas validadas é um componente útil; ele não define a transação do banco de dados, a autoridade sobre o tenant nem o procedimento de recuperação.

Se a tarefa imediata for uma importação administrativa no Supabase, use primeiro o passo a passo da importação integrada.

Comece com um contrato de importação por escrito

Defina o que uma linha representa e se a operação insere, atualiza ou faz upsert. São contratos diferentes. Para atualizações, a correspondência de identidade e as alterações permitidas precisam de aprovação própria. Evite um upsert genérico só porque ele faz os erros de duplicidade desaparecerem.

Um contrato conciso para contatos poderia dizer:

Contrato ilustrativo
Operation: insert new contacts
Target: one approved application table
Source fields: external_contact_ref, email, display_name
Tenant: supplied by an authorized import job
Primary key: generated by the database
External identity: unique within tenant + source system
Missing display name: NULL
Existing external identity: report conflict; do not overwrite
Accepted set: frozen after review; edits require revalidation

Registre também os limites e formatos de arquivo suportados, as regras de transformação, a versão do esquema, a política de erros e as evidências necessárias para concluir a importação. Isso é um artefato de engenharia. Ele não precisa de uma linguagem de configuração nem de um compilador.

Uma versão vincula a prévia ao comportamento que será executado. Se uma regra de mapeamento ou normalização mudar após a confirmação, invalide a prévia antiga. Se o esquema de destino mudar, reavalie a compatibilidade em vez de pressupor que as colunas revisadas anteriormente continuam com o mesmo significado.

Defina limites de arquivo e parsing

Escolha onde o parsing acontece. O parsing no navegador pode manter um arquivo exploratório local e tornar as prévias responsivas. O parsing no servidor pode dar suporte a tarefas que continuam após o fechamento da aba. As duas abordagens precisam de limites de recursos; a validação feita no navegador deve ser repetida por um componente com autoridade antes de uma gravação.

Defina limites de bytes, registros, colunas, comprimento de células, tempo de processamento e detalhes de erro retidos. Para XLSX, o tamanho do arquivo compactado sozinho não limita o tamanho descompactado, o número de abas nem o número de células. Use streaming ou transfira o processamento quando adequado e interrompa com uma mensagem útil quando os limites de arquivo ou processamento forem excedidos.

Para uploads, autentique o acesso, gere sua própria chave de armazenamento e isole os arquivos por tarefa autorizada. Não confie em um nome de arquivo como caminho. Explicite a retenção de uploads abandonados, tarefas concluídas, tarefas com falha e backups. Na prática, um arquivo armazenado “temporariamente” sem um mecanismo de exclusão fica armazenado indefinidamente.

Suportar apenas CSV é uma primeira versão legítima. Se houver suporte a pastas de trabalho, deixe o usuário escolher a aba e a linha de cabeçalho, documente o comportamento de fórmulas e valores em cache e não execute macros da planilha nem links externos. Informe ao usuário o que foi ignorado.

Preserve os valores durante o parsing

Use um parser mantido em vez de separar o texto por vírgulas ou quebras de linha. A RFC 4180 descreve delimitadores e quebras de linha entre aspas, mas exportações reais ainda diferem em codificação, delimitadores e tratamento de valores em branco. Aceite um conjunto documentado e mostre a interpretação detectada antes da execução.

Mantenha identificadores como strings. A conversão numérica automática pode remover zeros à esquerda ou arredondar um número de conta longo. Opções de parser como tipagem dinâmica, tratamento de cabeçalho e relato de erros do Papa Parse estão documentadas na referência da API; escolha-as deliberadamente.

Caso de testeComportamento esperado a definir
Vírgula ou quebra de linha dentro de uma célula entre aspasUm campo / um registro, sem coluna ou registro extra
BOM UTF-8 e nomes com caracteres fora do ASCIICabeçalho reconhecido; nomes preservados
Cabeçalho duplicado ou dois cabeçalhos mapeados para um campoConflito explícito, sem sobrescrita silenciosa
00042 e uma referência externa de 20 dígitosPreservação exata da string
Célula vazia, célula vazia entre aspas, campo ausenteSemânticas distintas ou unificadas intencionalmente, com documentação
Data ambígua ou decimal em formato regionalPeça a interpretação; não adivinhe
Aspas malformadas ou quantidade inesperada de camposDiagnóstico limitado com localização na origem

Acompanhe um identificador do registro de origem ao longo das correções e gravações. Números de linha física ajudam a localizar erros do parser, mas um registro com várias linhas entre aspas pode ocupar diversas linhas. Um resultado deve permitir voltar ao registro de origem sem tratar esses conceitos como se fossem sempre intercambiáveis.

Permita o mapeamento apenas para campos graváveis

Apresente campos de destino que a origem realmente tenha permissão para fornecer. Sugestões podem poupar digitação; não devem criar novas permissões de gravação. Exija uma resolução para campos obrigatórios ausentes, mapeamentos conflitantes e colunas de origem excluídas.

Um cabeçalho de origem como “Responsável” pode significar um vendedor, um workspace ou o criador do registro antigo. Mostre a descrição do destino e valores de exemplo, não apenas uma pontuação de similaridade. Guarde o mapeamento confirmado junto à tarefa para que o suporte possa explicar um resultado depois.

Rejeite novamente chaves desconhecidas no servidor. Construa as gravações no banco a partir dos campos de destino aprovados, nunca de chaves arbitrárias da requisição. Parametrize os valores e use uma lista de identificadores permitidos revisada quando identificadores dinâmicos forem inevitáveis. Um nome de arquivo ou cabeçalho não deve se tornar SQL executável.

Classifique separadamente valores padrão, identidades e expressões geradas. Um valor padrão comum não torna um campo protegido. Veja valores fornecidos pelo banco de dados durante a importação de CSV para um exemplo executável.

Separe normalização de decisões de negócio

A normalização aplica uma interpretação aprovada. Uma decisão de negócio escolhe essa interpretação. Remover espaços de um formato de identificador acordado pode ser mecânico; decidir que “ex-cliente” corresponde a “inativo” exige autoridade sobre o domínio. Trate um fuso horário inferido, a substituição de um enum ou a escolha do registro a manter entre duplicados como uma alteração proposta até que seja aprovada.

Valide em camadas, com um erro útil no primeiro ponto confiável:

  1. Estrutura do arquivo: codificação suportada, registros, cabeçalhos e limites.
  2. Valores dos campos: obrigatoriedade, tipos, faixas e formatos aceitos.
  3. Relações dentro do arquivo: duplicatas e referências repetidas inconsistentes.
  4. Regras de negócio: status permitidos, semântica de identidade e permissões.
  5. Verificações no destino: restrições aplicáveis, existência de referências e comportamento real de gravação.

Uma consulta ao destino é um fato sobre um momento específico. Outra transação pode inserir o mesmo valor único entre a prévia e a execução. Mantenha a restrição como decisão final e trate sua falha; uma prévia sem erros não é uma reserva.

Os erros devem identificar o registro, o campo, a regra e uma possível correção sem registrar valores sensíveis nos logs. Agrupe problemas repetidos para que o usuário possa corrigir um problema sistêmico de uma só vez. A decisão de excluir registros precisa de uma contagem e de um motivo visíveis; descartar linhas silenciosamente cria uma divergência sem explicação depois.

Mostre o resultado proposto antes da gravação

Uma prévia útil mostra as contagens de registros aceitos e excluídos, as correções necessárias, as transformações aprovadas, o tenant de destino e a operação. Informe quais verificações foram executadas contra um destino ativo e quais foram estáticas ou locais.

Chame uma etapa de validação sem gravação exatamente assim. Uma transação que faz gravações reais e depois as reverte não é necessariamente um ensaio sem efeitos colaterais: alocações de sequências e efeitos iniciados em sistemas externos podem não ser revertidos. Ensaie o comportamento de gravação em um ambiente isolado criado para essa finalidade.

Mantenha a confirmação vinculada ao arquivo, ao mapeamento, às regras e ao conjunto de registros aceitos. Editar uma célula após a confirmação deve criar uma nova proposta validada. Para entradas grandes, virtualize a grade de correção se necessário, preservando o acesso pelo teclado, os rótulos dos campos e um resumo fora das linhas virtualizadas.

Resolva a autoridade sobre o tenant e as chaves estrangeiras

Vincule a tarefa a um tenant e a um operador autorizados. Forneça os campos controlados pelo tenant por meio do componente de gravação confiável. Um campo oculto no navegador continua sendo uma entrada controlada pelo navegador. Se a RLS fizer parte do projeto, teste com o papel de execução e o conjunto de políticas reais; o acesso com service role muda essa fronteira.

Uma referência como “Empresa 42” geralmente identifica uma linha no sistema antigo. Resolva uma chave externa aprovada dentro do namespace correto de tenant e origem. Nenhuma correspondência e várias correspondências são falhas diferentes; escolher a primeira linha esconde a ambiguidade. Mantenha ativas as restrições finais de chave estrangeira e de consistência de tenant.

Os guias detalhados sobre autoridade sobre o tenant e resolução de chaves estrangeiras mostram essas fronteiras. A ordem entre várias tabelas e os ciclos exigem um plano relacional explícito; não são recursos de mapeamento de colunas.

Confirme as gravações e seu comprovante juntos

Para uma importação pequena e de tamanho limitado, uma única transação costuma ser o modelo de resultado mais simples. Para trabalhos maiores, escolha os lotes com base em medições de duração da transação, memória, bloqueios e carga do banco. Um tamanho de lote copiado de um tutorial não é um plano de capacidade.

Dê a cada tarefa e lote uma identidade estável. Uma nova tentativa idêntica deve retornar o mesmo resultado registrado; um payload alterado usando a mesma identidade deve ser rejeitado. O comprovante e as gravações no destino precisam ser confirmados na mesma transação para que o comprovante ateste essas gravações.

Algoritmo ilustrativo de transação no PostgreSQL
BEGIN
  authorize access to this job
  lock the existing job row FOR UPDATE
  look up receipt for (job_id, batch_number)
  if receipt exists:
    require matching payload hash and contract version
    return recorded result without inserting again
  else:
    check job state and the next expected batch
    validate and insert the approved rows
    insert receipt with identity, hash and result
    advance the job's committed progress
COMMIT

Este é um esboço de projeto, não SQL executável. O bloqueio da linha da tarefa serializa tentativas concorrentes para essa tarefa, e uma chave única de comprovante fornece uma restrição adicional no banco. A autorização também se aplica à leitura de um comprovante existente. Defina uma representação estável para o hash que inclua os valores aceitos e a identidade relevante do contrato.

Não coloque um comprovante em um banco e as gravações de destino em outro para então chamar o par de atômico. Se precisar atravessar sistemas, especifique um protocolo de reconciliação para essa lacuna. Da mesma forma, a confirmação de recebimento de uma fila e o commit de um banco não são uma única transação só porque ocorrem na mesma função.

Se notificações ou webhooks precisarem ocorrer após um commit, uma outbox gravada na transação pode registrar entregas pendentes. A entrega ainda precisa de suas próprias regras de idempotência e novas tentativas. Mantenha o conteúdo das linhas importadas fora dos payloads rotineiros de eventos.

Represente resultados parciais e desconhecidos com precisão

Uma resposta perdida não comprova que a transação falhou. Consulte um comprovante persistente ou tente novamente o lote idêntico pelo caminho idempotente. Se não for possível acessar essa evidência, mostre um resultado não resolvido e interrompa uma repetição cega de tudo.

Se os lotes 1–4 foram confirmados e o lote 5 falhou, informe o subconjunto confirmado e o restante que falhou ou não foi tentado. Um botão de cancelamento pode interromper trabalhos futuros; ele não pode prometer desfazer lotes confirmados. Uma operação de desfazer precisaria de uma alteração compensatória revisada separadamente, incluindo os efeitos sobre referências e edições posteriores.

Explicite o comportamento que tolera falhas em linhas individuais. “Ignorar linhas inválidas” significa que o cliente aceita uma importação parcial e consegue identificar os registros ignorados. Lotes do tipo tudo ou nada são uma alternativa adequada, especialmente quando objetos de domínio parciais seriam enganosos. Nenhuma das opções deve ficar escondida atrás de uma mensagem genérica de sucesso.

Tarefas em segundo plano se tornam úteis quando timeouts de requisição, desconexões do navegador ou o tempo de processamento tornam a execução síncrona inadequada. Persista o estado necessário para retomar, limite novas tentativas e ofereça uma forma de inspecionar ou interromper trabalhos travados. Uma fila não fornece idempotência automaticamente.

Defina a conclusão e um caminho de suporte

Reconcilie o conjunto aceito com os registros confirmados usando chaves estáveis e depois compare campos importantes e invariantes do domínio. As contagens são a primeira verificação. Elas não detectam conjuntos de mesmo tamanho com registros diferentes. O guia de verificação demonstra essa falha com SQL.

Registre nos logs os identificadores de tarefas, versões, contagens, categorias de erro, durações e transições persistentes de estado. Oculte informações sensíveis dos erros do banco antes de apresentá-los se puderem revelar dados de outro tenant. Restrinja o acesso aos artefatos de diagnóstico e registre quando devem ser excluídos.

Ofereça uma página de resultado útil mesmo após o fechamento da aba: o que foi aceito, o que foi confirmado, o que continua sem resolução e o que o operador pode fazer em seguida. Arquivos de correção exportados também precisam tratar fórmulas de planilha; colocar campos entre aspas conforme a sintaxe CSV não impede, por si só, que uma planilha interprete uma célula perigosa como fórmula.

Antes do lançamento, teste uma resposta de commit perdida, dois envios simultâneos do mesmo lote, um payload alterado com uma chave antiga de nova tentativa, uma permissão de tenant revogada, uma mudança de esquema após a prévia e uma tarefa cancelada com lotes já confirmados. Esses casos revelam mais sobre o importador do que um grande arquivo de demonstração sem problemas.

Fontes e leituras complementares