Ir para o conteúdo

Projeto independente de P&D · Colônia

Java e Spring

Tipos selados, Bean Validation, Micrometer, Spring Modulith e ArchUnit, e os pontos em que uma propriedade declarada deixa de valer.

Não normativo

Versão do Companion
1.0
Corresponde ao COADF Core
2.2
Status
Atual
Última revisão
Versão do perfil
1.0
Exemplos de código
Os exemplos de código virão em uma revisão posterior.

Os exemplos de código virão em uma revisão posterior.

Propriedades de arquitetura tratadas

  • P-1Uma hierarquia selada que separa propostas de valores verificados, validação na fronteira web e regras de módulo que o build verifica.
  • P-4Micrometer Observation e Tracing para o contexto de execução, a identidade de auditoria em todo contrato e entradas de auditoria escritas dentro da transação de negócio.
  • P-5Procedência como campos dos tipos de valor, serializada explicitamente.
  • P-6Verificação de arquitetura na fase de testes, e checks obrigatórios que não podem passar por terem sido pulados.
  • P-7Portas no módulo de domínio e adaptadores em módulos próprios.
  • P-8Uma interface de política com um adaptador para o motor, e decisões registradas com sua revisão.
  • P-2, P-3Apenas na profundidade que o COADF publica.

Intenção de arquitetura

A força do Spring para essas propriedades é a declaração: restrições sobre tipos, transações e validação aplicadas pelo contêiner, observações configuradas uma única vez, regras de módulo checadas por um teste. Sua falha característica é o outro lado do mesmo design. Uma declaração vale nos caminhos que o contêiner intercepta, por meio de um proxy, em um binding web ou em um executor que ele configurou, e em nenhum outro lugar. Uma propriedade escrita como anotação só é tão forte quanto o conjunto de chamadas que de fato passam pelo Spring.

Este perfil mapeia cada propriedade para o Spring Boot 4 e o Spring Framework 7, a geração atual quando foi escrito, e nomeia os pontos em que uma propriedade declarada deixa de valer em silêncio.

Correspondência tecnológica

Java e Spring: Correspondência tecnológica
Propriedade de arquiteturaJava e Spring
Tipo de fronteiraUma interface sealed com implementações record: um tipo de proposta e um tipo verificado, construídos em módulos diferentes. Records são portadores de dados com imutabilidade rasa: um componente que é uma lista mutável continua mutável, por isso ele deve ser copiado no construtor. Classes seladas são um recurso padrão desde o Java 17.
Validação em tempo de execuçãoRestrições do Jakarta Validation nos tipos de requisição, disparadas com @Valid. Uma validação de @RequestBody que falha lança MethodArgumentNotValidException, respondida por padrão com 400.
Campos desconhecidosUma decisão explícita e testada, nos tipos de fronteira, sobre propriedades que o tipo não declara, em vez do que quer que a configuração do mapeador JSON seja no momento.
Regra de arquiteturaO ApplicationModules.verify() do Spring Modulith, ou regras do ArchUnit escritas como testes unitários.
Contexto de execuçãoMicrometer Observation e Micrometer Tracing, com ponte para o OpenTelemetry.
Contexto assíncronoContextPropagatingTaskDecorator com a biblioteca context-propagation do Micrometer; para o executor autoconfigurado, a propriedade do Boot spring.task.execution.propagate-context.
Trilha de auditoriaEntradas escritas na transação de negócio; grants e triggers do banco de dados como no exemplo de PostgreSQL; o registro de publicação de eventos do Spring Modulith para eventos que não podem se perder.
Isolamento de normasInterfaces de porta no módulo de domínio; um módulo adaptador por sistema externo; o cliente do fornecedor nunca visível para o domínio.
PolíticaUma interface de política pertencente ao domínio, com um adaptador para o Open Policy Agent, ou tabelas de regras; a revisão armazenada na decisão.
Testes de integraçãoUm PostgreSQL real via Testcontainers, conectado com @ServiceConnection.

Padrão de referência

Os exemplos de código virão em uma revisão posterior. Eles ficam adiados até que uma pessoa revisora que trabalhe neste ecossistema os tenha lido, em vez de serem escritos para que os quatro perfis pareçam iguais. A estrutura abaixo é o padrão de referência que esses exemplos vão implementar.

Módulos

  • inference chama o modelo e devolve um Proposal, um record que carrega o valor, o método e a procedência. Ele não tem dependência de records nem de audit, e uma verificação do Modulith ou uma regra do ArchUnit afirma isso em um teste.
  • records é o único módulo que pode criar um VerifiedValue. O tipo não é público fora do módulo, ou é uma classe cujo construtor não é público: um record público não pode esconder seu construtor, porque o construtor canônico de um record público precisa ser, ele próprio, público. A criação passa por uma factory que exige a verificação de uma pessoa nomeada.
  • audit acrescenta entradas com os campos publicados, dentro da transação da mudança que registra, por meio de um repositório que não oferece atualização nem exclusão.
  • classification declara a porta; classification.vendor a implementa, e é o único módulo que pode ver o cliente do fornecedor ou seus tipos.
  • policy declara a interface de política e o registro de decisão; um módulo adaptador conversa com o motor.

Fronteiras em tempo de execução

A validação acontece no binding web, onde o Spring avalia as restrições sobre o corpo da requisição, e de novo dentro da factory do domínio, porque o binding web é um ponto de entrada entre vários: um listener de mensagens, um job em lote e um teste chegam todos ao domínio sem passar por ele.

As observações envolvem a fronteira e as chamadas externas. Todo executor que roda trabalho para uma requisição é configurado para propagar o contexto, e a identidade de auditoria é um campo de toda mensagem e de todo argumento de job, nunca recuperada de uma thread-local depois do fato.

Modos de falha

  1. A autoinvocação contorna o proxy

    O Spring AOP é baseado em proxy: uma chamada de um método de um bean para outro método do mesmo bean não passa pelo proxy, então a validação de métodos e o @Transactional não se aplicam a ela. Uma restrição que vale para quem chama de fora do bean não vale para o próprio bean, que muitas vezes é onde começa o caminho em lote.

  2. Restrições declaradas, nunca avaliadas

    Restrições do Jakarta Validation em um tipo não fazem nada por si mesmas. Elas são avaliadas onde algo dispara a validação: @Valid ou @Validated em um binding web, ou a validação de métodos por meio do proxy. Um listener de mensagens que recebe o mesmo tipo não valida nada, a menos que peça.

  3. Propriedades não declaradas deixadas à configuração do mapeador

    Se um documento JSON recebido pode carregar propriedades que o tipo não declara é uma configuração do mapeador JSON. Para um tipo de fronteira, a resposta certa costuma ser recusá-las, e essa decisão pertence ao tipo e a um teste, não a uma propriedade da aplicação que outra pessoa pode mudar.

  4. Imutabilidade do ORM tomada por salvaguarda de auditoria

    O Hibernate ignora mudanças em memória em uma entidade @Immutable gerenciada, sem update e sem exceção. Atualizações em massa contra ela lançam exceção por padrão no Hibernate 7 e apenas geravam um aviso no 6.6. Nada disso impede que um segundo cliente, um script ou uma migração atualize a tabela; a proteção pertence ao banco de dados.

  5. Eventos de auditoria perdidos depois do commit

    Um evento tratado depois do commit da transação se perde se o handler falhar, a menos que algo o tenha registrado. O registro de publicação de eventos do Spring Modulith escreve uma entrada na transação que publica e mantém as publicações que falharam para reenvio; republicá-las automaticamente ao reiniciar é uma configuração que precisa ser ligada.

  6. Um record público como tipo verificado

    O construtor canônico de um record público precisa ser público, então qualquer código que enxergue o tipo pode construir um valor verificado sem uma verificação. O tipo deve ficar fora de alcance, ou ser uma classe com construtor privado e uma factory.

  7. Duas pontes de tracing

  8. Testes de integração contra outro banco de dados

    Grants, triggers e o comportamento das restrições são propriedades do banco de dados real. Testes contra um substituto embarcado verificam o código, e não a propriedade.

Verificação

  • Teste unitário

    Passa quando: O tipo verificado só pode ser criado pela factory, e só com a verificação de uma pessoa nomeada; uma rejeição deixa o atributo vazio.

    Prova de dentes: Tornar o tipo verificado público em uma branch descartável: um teste que afirma que ele não é acessível a partir do módulo de inferência falha.

  • Teste de arquitetura

    Passa quando: A verificação do Spring Modulith passa, ou passam as regras do ArchUnit: nenhuma dependência de inference para records ou audit, nenhum tipo de fornecedor no domínio. O ArchUnit checa dependências entre pacotes e classes como testes unitários comuns, e a verificação do Modulith rejeita ciclos entre módulos e referências a pacotes internos de outro módulo, exceto a módulos declarados abertos. Qualquer das duas ferramentas checa as regras que foram escritas; se são as regras certas é uma questão do design da equipe.

    Prova de dentes: Plantar a dependência proibida: a verificação falha.

  • Teste de integração

    Passa quando: Contra um PostgreSQL do Testcontainers: o papel da aplicação não consegue atualizar nem apagar a trilha, e as entradas são escritas na mesma transação que a mudança.

    Prova de dentes: Fazer rollback da transação de negócio depois de escrever: a entrada de auditoria não pode sobreviver a ele.

  • Teste de integração

    Passa quando: O contexto sobrevive aos executores que a aplicação usa: um exportador em memória mostra o trabalho assíncrono como parte do mesmo trace, e a identidade de auditoria está presente no seu registro.

    Prova de dentes: Remover o task decorator: o teste falha.

  • Teste de contrato

    Passa quando: Produtor e consumidor concordam sobre os contratos de proposta e de decisão, com a procedência e a revisão como campos obrigatórios.

  • Teste de ponta a ponta

    Passa quando: Uma saída cujo atributo não foi verificado não pode ser publicada, seja qual for o ponto de entrada por onde a requisição chegou.

Realizações alternativas

  • Outros frameworks Java (Quarkus, Micronaut, Jakarta EE) expressam a mesma fronteira com modelos de interceptação diferentes; a questão da autoinvocação precisa ser colocada para cada um.
  • SQL explícito (jOOQ ou JDBC puro) para a trilha de auditoria em vez de um ORM, onde as instruções exatas importam mais do que a conveniência do mapeamento.
  • Frameworks de event sourcing onde o histórico é o modelo.
  • Tabelas de regras mantidas pela aplicação em vez de um motor de políticas externo, com a mesma exigência quanto à revisão da política.

Escolhas de compromisso

  • O declarativo é conciso e invisível. Quem lê não consegue ver, no ponto de chamada, se uma restrição ou uma transação se aplica; os testes precisam mostrar isso.
  • O Spring Modulith codifica convenções, o que é rápido de adotar e opinativo; o ArchUnit é genérico e exige que as regras sejam escritas por extenso.
  • O JPA é conveniente e põe uma camada entre o código e as proteções do próprio banco de dados. Para a trilha de auditoria, essa camada é a parte através da qual é preciso enxergar.
  • Fronteiras de módulo dentro de um único artefato implantável mantêm simples o modelo operacional, e fazem a fronteira depender inteiramente da verificação do build.

Limitações

  • Nenhum código é publicado para este perfil no Companion 1.0, e nenhum exemplo foi executado. O comportamento descrito foi tirado da documentação oficial atual, conferida em 11 de setembro de 2026, do Spring Boot 4.1, Spring Framework 7.0, Spring Modulith 2.1, Hibernate ORM 7.4 e Java SE 25.
  • Nada aqui mostra como a confiança é representada, quando a revisão é exigida ou como a revisão é organizada; o COADF não publica essas partes de P-2 e P-3.
  • Tudo o que é citado é comportamento de linguagem ou de framework. A propriedade de arquitetura, a de que uma proposta não pode se tornar um registro sem uma verificação, é um design que a equipe ainda precisa fazer e testar: nenhuma anotação, record ou regra de módulo a fornece por si só.

O que este perfil não estabelece

Seguir este perfil não estabelece conformidade regulatória, certificação nem avaliação da conformidade, e o Spring não é exigido pelo COADF.

Ambiente de referência testado

Nenhum exemplo foi executado para este perfil.

Fontes

COADF Engineering Companion 1.0 · não normativo · corresponde ao COADF Core 2.2

Direitos de publicação reservados. Nenhuma licença pública é concedida, no momento, para o COADF Engineering Companion 1.0 nem para seus exemplos de referência.

Status de propriedade intelectual e de publicação