0030 — Adota o package-by-feature¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-14 · Decidido em: 2026-09-14
Contexto¶
A norma da casa é package-by-feature: controller, service, repository e entidade convivem no pacote da
feature, e o transversal vive numa base comum
(Java/Micronaut, Arquitetura). O
bi-comercial-xls estava organizado por camada — api/, service/, repository/, processing/,
domain/, dto/, security/ e exception/ —, e o processador irmão, bi-transporte-xls, já tinha
migrado (decisão 0022 daquele repo). A migração para a constituição 2.0.0 é o refactor estrutural em que a
troca entra.
Duas classes misturavam features e impediam o corte limpo: o TelegramService montava as mensagens do
processamento e do nuke, e o StartupRecoveryService recuperava os dois no boot. Em qualquer fatia, cada
uma forçaria uma dependência de volta para a outra feature.
Decisão¶
O código é package-by-feature sob br.com.onpetro.bi.comercial, com as camadas (controller, service,
repository, entidade, DTO, view-model) dentro da fatia. Na raiz ficam só Application e BearerTokenEnv.
| Fatia | Papel |
|---|---|
processamento |
o pipeline XLS: API de ingestão e consulta, telas /processamentos/**, recepção do .zip, fila assíncrona, lote, storage do binário e backfill, mensagens de Telegram do processamento e o recovery no boot; entidades xls_processamento e xls_conjunto_lote |
processamento.planilha |
a leitura das planilhas: ArquivoDetector, TipoArquivo, ExcelSheetReader, ZipXlsExtrator, Segmento e os 9 {Tipo}Processor |
dados |
as tabelas bi_* de negócio: entidades, repositórios e a escrita em lote (BulkUpserter, ColunasConteudo, UpsertResultado) |
importacao |
a página aberta de import manual (/xls/importar) — ImportacaoXlsController |
powersync |
o reset (nuke) da replicação: orquestrador, API REST, tela /admin, Mongo, slot, Coolify, auditoria, mensagens de Telegram do nuke e o recovery no boot |
configuracao |
bi_configuracao: leitura com cache e a tela /admin/configuracoes |
diagnostico |
as rotas /test e /test/glitchtip |
seguranca |
a policy de acesso das views: ViewSecurityRule, ViewWhitelist e os handlers de rejeição |
comum |
base transversal, não é feature: comum.exception (StorageIndisponivelException, 503) e comum.notificacao (TelegramService) |
%% lint-mermaid: LR-ok
flowchart LR
I[importacao] --> P[processamento]
P --> PL[processamento.planilha]
PL --> D[dados]
P --> D
P --> C[comum]
PS[powersync] --> CF[configuracao]
PS --> C
dadosé folha: não depende de nenhuma fatia. Quem grava asbi_*depende dela, nunca o contrário.comumnão conhece feature. O Telegram se parte em transporte e formatação: ocomum.notificacao.TelegramServicesó envia texto pronto; oProcessamentoNotificadore oNukeNotificadormontam as mensagens da sua feature. O recovery de boot se parte do mesmo jeito:ProcessamentoStartupRecovery(PROCESSANDOviraERRO,PENDENTEvolta à fila) eNukeStartupRecovery(nuke órfão viraERRO), ambos atrás deapp.recovery.enablede depois do backfill do storage (@Order(-100)).processamento.planilhanão conhece o pipeline: depende dedados, nunca do pacoteprocessamento(fila, lote, storage, controllers).- A exceção local de storage mora em
comum.exception; as exceções HTTP genéricas vêm daxadm-comum-web. A decisão 0012 fica obsoleta. - O
ComprasImportControllerpassa aImportacaoXlsController: o nome antigo era cosmético e o rename estava deferido (0019). - Os testes espelham as fatias; ficam na raiz os transversais (
ArchitectureTest, bases de teste,TestXlsxFactory,/health, RFC 7807, serde).
Travas ArchUnit (ArchitectureTest, no check, sobre a RegrasArquitetura da xadm-comum-teste):
import não-vazio; sem ciclos entre as fatias; comum não depende de fatia; dados é folha;
processamento.planilha não depende do pacote processamento; controller não acessa repository; nada
depende de controller; SQL cru pela conexão só nas classes de bulk da allowlist (as de dados e
powersync.PostgresReplicationAdmin); entidade livre de infra; escrita de controller de view declara
@Consumes. As regras de fronteira citam o nome completo da fatia: o padrão curto (..seguranca..)
casaria também o pacote br.com.xadm.comum.seguranca da lib.
Consequências¶
- O código de uma feature fica junto; o transversal fica explícito em
comum, e o ArchUnit barra a volta de dependência por descuido. - Migração mecânica (
git mv, pacote e imports), sem mudança de contrato: rotas, JSON, OpenAPI, schema e envs seguem iguais. - A guarda foi provada na migração: uma violação temporária por regra de fronteira reprovou cada uma, e o verde voltou ao desfazê-las. Rename futuro de pacote leva o literal da regra junto e repete a prova (norma, Rename de pacote).
- As telas de admin recebem view-model (
NukeView,ConfiguracaoView), nunca a entidade. bi-comercial-xlsebi-transporte-xlsficam no mesmo layout; o corte difere onde o domínio pede — lá asbi_*moram emprocessamento, aqui são a fatiadados.
Alternativas consideradas¶
- Manter package-by-layer como exceção (a versão anterior desta decisão, não publicada): descartada. A exceção não tinha motivo de domínio, e o custo do rename é o mesmo agora ou depois.
bi_*dentro deprocessamento, como nobi-transporte-xls: descartada. Como fatia folha, o modelo de BI se lê sem puxar o pipeline, e a regra "a planilha grava sem conhecer o pipeline" fica verificável.