Pular para conteúdo

0026 — Migrar leitura de XLSX de POI para FastExcel (native-image)

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-11 · Decidido em: 2026-08-19

Supersede a decisão 0002 (leitura via POI/SAX).

Contexto

O alvo é deployar o binário GraalVM native-image no lugar do jar — cortar RAM e tempo de startup no servidor (Coolify). O bloqueio era o Apache POI: ele lê XLSX via XMLBeans, cujo schema type-system é carregado por reflexão + recursos .xsb e não compila em native-image (ClassCastException no StylesTable, sem fix estável no GraalVM CE). Enquanto POI estivesse no grafo — inclusive só nos testes, arrastando o bridge log4j-to-slf4j — não havia native limpo.

A decisão 0002 já resolvia o problema de memória lendo em streaming (SAX, memória ~constante). O requisito novo (native) não invalida aquele — apenas exige outra lib de leitura streaming que compile em native.

Decisão

Ler XLSX com FastExcel (org.dhatim:fastexcel-reader), que usa apenas o StAX do JDK (zero XMLBeans) e compila limpo em native, preservando o streaming linha-a-linha.

  • ExcelProcessor reescrito sobre ReadableWorkbook/Sheet.openStream(), mantendo a semântica de célula idêntica ao reader SAX anterior (string→trim/null; numérico com formato de data→ ddMMyyyy; numérico comum→bruto; boolean; erro/vazio→null). A paridade é garantida pelo golden-master ProcessamentoConformidadeTest (Java × Python sobre planilhas reais).
  • O path DOM legado (ExcelCellValue, SheetSaxHandler, AbaConfigProcessor, AbaFaturamentoProcessor, AbaMovimentoProcessor) foi removido. O período virou o tipo PeriodoRelatorio.
  • POI zero, inclusive nos testes: a escrita de fixture XLSX migrou para o FastExcel-writer (org.dhatim:fastexcel) via o helper de teste XlsxFixtureWriter. POI e log4j-to-slf4j saíram do build.gradle.kts por completo.
  • Build native configurado no build.gradle.kts (configure<GraalVMExtension>): perfil -PnativeQuick → -Ob (loop de dev), sem a flag → -Os (imagem menor, prod); --gc=serial. reflect-config.json registra o SentryAppender do logback (instanciado por reflexão no boot). Evolução (2026-09-11): desde a xadm-comum-web 0.9.1 essa hint vem no jar da lib, com os métodos, mais SentryOptions e Level.valueOf; o reflect-config.json local perdeu as entradas do appender e do SentryOptions.

Consequências

  • Native-image desbloqueado: dá pra buildar/deployar o binário no lugar do jar.
  • Memória ~constante mantida (streaming StAX), como na 0002.
  • isDateFormat é código nosso (reproduz o DateUtil.isADateFormat do POI, que saiu do classpath) — coberto por teste unitário direto (ExcelProcessorDateFormatTest).
  • Ramo de leitura de data numérica sem teste dedicado (defer explícito): as planilhas reais do cliente exportam datas como texto (verificado nas fixtures: zero célula com formato de data), e o roundtrip FastExcel writer→reader não preserva a detecção de formato-de-data (getDataFormatString() volta null). Logo não há como montar o input desse ramo via o XlsxFixtureWriter, e o ramo não dispara em produção. É código defensivo; mantido, mas sem teste de ponta a ponta — cobri-lo exigiria um .xlsx binário forjado por ferramenta externa para código de valor ~zero. Se um dia o cliente passar a exportar datas como células Excel, revisitar.

Correção 2026-09-15: o roundtrip preserva o formato quando o reader abre com ReadingOptions(true, false); sem o true, o getDataFormatString() volta null. O ExcelProcessor passou a abrir assim, e o ramo asDate() ganhou teste de ponta a ponta com a célula de data escrita pelo XlsxFixtureWriter.

Alternativas consideradas

  • Manter POI só em testImplementation: deixaria o objetivo (zero POI) pela metade e manteria o bridge log4j-to-slf4j; sem ganho, já que o FastExcel-writer cobre a escrita de fixture.
  • Forjar .xlsx binário com data real para cobrir o ramo asDate: reintroduz dependência de ferramenta externa (Excel/LibreOffice/POI) e um binário no repo para exercitar código que dado real nunca toca. Descartado (baixo valor).
  • Remover o ramo asDate/isDateFormat: perderia a defesa caso o export do cliente mude. Descartado — mantido como defensivo.