Menu fechado

Escalando ArchUnit com Nebula ArchRules na Netflix

ArchUnit

🚀 Introdução: O Desafio da Escala na Netflix

Na Netflix, operamos sob uma estratégia de polyrepo, gerenciando dezenas de milhares de repositórios Java. Essa arquitetura descentralizada impõe um desafio crítico: a necessidade de compartilhar lógica de build consistente entre todos esses projetos. Para mitigar essa complexidade, a equipe de JVM Ecosystem, parte da plataforma Java da Netflix, desenvolve o conjunto de plugins Gradle Nebula. O objetivo é padronizar a construção de projetos, manter dependências atualizadas e garantir a publicação confiável de artefatos em todo o ecossistema.

A missão vai além da automação; busca-se fornecer feedback imediato durante o tempo de compilação, garantindo que o código permaneça alinhado às melhores práticas — o que chamamos de paved road — e identificando precocemente o acúmulo de dívida técnica através de análise estática contínua.

📦 Gerenciando o Ciclo de Vida de APIs

O surgimento do Nebula ArchRules foi motivado por incidentes operacionais, como a liberação de mudanças incompatíveis em bibliotecas, onde autores de código enfrentavam dificuldades para determinar o momento seguro de remover APIs obsoletas ou refatorar componentes internos. Para resolver isso, estabelecemos um conjunto de anotações de ciclo de vida (@Deprecated, @Public e @Experimental), mas era imperativo implementar um mecanismo robusto para identificar quando projetos consumidores utilizavam essas APIs de maneira indevida.

A solução encontrada foi o ArchUnit, uma biblioteca de análise estática baseada em bytecode (via ASM). Diferente de ferramentas tradicionais que dependem de árvores de sintaxe abstrata (AST), o ArchUnit permite criar regras arquiteturais poderosas, tipadas e de fácil teste. Ao integrar o ArchUnit com o ecossistema Gradle através do Nebula, conseguimos escalar a aplicação dessas regras, transformando o que antes era uma análise restrita a um único repositório em uma ferramenta organizacional capaz de auditar e governar o ciclo de vida de APIs em toda a frota de microsserviços.

🛡️ Por que escolher o ArchUnit?

Antes de implementar o ArchRules, é fundamental compreender por que o ArchUnit supera as ferramentas tradicionais de análise estática em cenários complexos de engenharia de software, especialmente em ambientes de alta disponibilidade e escala massiva.

⚙️ Análise via Bytecode (ASM) vs. AST

Ferramentas convencionais, como PMD ou Checkstyle, operam sobre uma Árvore de Sintaxe Abstrata (AST). Essa abordagem é inerentemente dependente da sintaxe da linguagem, exigindo reescrita de regras para cada variante da JVM (como Kotlin ou Scala) e sendo vulnerável a “açúcar sintático” que oculta a intenção real do código. O ArchUnit, por outro lado, utiliza a biblioteca ASM para analisar o bytecode compilado. Isso garante que a análise seja agnóstica à linguagem de origem e foque no código que será efetivamente executado pela JVM, eliminando ambiguidades sintáticas e garantindo consistência entre diferentes linguagens que compõem o ecossistema JVM.

🧩 Escrita de Regras com API Fluida e Tipada

Diferente de ferramentas que dependem de parsers complexos (como XPath para PMD), onde a criação de regras é opaca e difícil de testar, o ArchUnit oferece uma API fluida, tipada e orientada a objetos. Isso permite que desenvolvedores criem regras complexas utilizando Java puro, com suporte total de IDE (autocompletar e verificação de tipos). Veja um exemplo de como uma regra de validação de construtores se torna legível e testável:


ArchRuleDefinition.priority(Priority.MEDIUM)
.noClasses()
.should()
.callConstructorWhere(
target(doesNot(have(rawParameterTypes(DateTimeZone.class))))
.and(targetOwner(assignableTo(DateTime.class)))
)

🔗 Contexto através de Relações entre Classes

Como o ArchUnit processa todo o classpath via ASM, ele mantém um grafo estruturado dos dados das classes. Essa característica permite que as regras não apenas verifiquem propriedades isoladas, mas naveguem por relacionamentos, dependências e pontos de chamada (call sites). Esse nível de contexto é o que possibilita a identificação precisa de dívidas técnicas e violações arquiteturais, como acoplamento cíclico ou acesso indevido a pacotes internos, que ferramentas baseadas apenas em análise de texto ou sintaxe simples seriam incapazes de detectar.

⚙️ Implementação e Execução de Regras

O plugin ArchRules Runner é o componente central que viabiliza a aplicação de políticas arquiteturais em escala. Sua principal função é automatizar a descoberta e a execução de regras, garantindo que o código seja validado sem a necessidade de configuração manual em cada repositório, promovendo a governança descentralizada.

📂 Configuração de Classpath e Descoberta

O plugin opera criando configurações de classpath específicas para cada source set do projeto. Ao integrar uma biblioteca de regras, o plugin combina o runtimeClasspath do projeto com a variante arch-rules da biblioteca. Essa abordagem utiliza o ServiceLoader do Java para identificar automaticamente as implementações da interface ArchRulesService. O processo é transparente: ao declarar uma dependência de regras, o plugin gerencia a resolução e garante que as definições de regras estejam disponíveis no contexto de execução correto, evitando conflitos de dependências transitivas.

🔒 Isolamento e Execução

Para garantir a integridade e evitar conflitos de dependências, o ArchRules Runner executa a avaliação das regras em um ambiente de isolamento de classpath, utilizando uma action de trabalho do Gradle. Durante essa etapa, o motor do ArchUnit processa o bytecode das classes do source set alvo. O isolamento permite que o plugin avalie o código de forma independente, gerando um relatório binário de violações que é posteriormente processado para exibição, garantindo que a execução das regras não interfira no processo de build principal.

⚖️ Customização e Controle de Qualidade

A flexibilidade é um pilar fundamental da ferramenta. Através da extensão archRules no arquivo de build, as equipes podem ajustar o comportamento das regras conforme a necessidade do projeto:

  • Priorização: É possível sobrescrever o nível de prioridade de uma regra (ex: priority("HIGH")), permitindo que regras críticas recebam maior atenção e bloqueiem o pipeline de CI.
  • Falha no Build: O plugin permite configurar limiares de falha, onde violações de alta prioridade podem interromper o build, garantindo que o débito técnico não seja introduzido no repositório.
  • Escopo: O desenvolvedor pode desabilitar a execução de regras específicas em determinados source sets, adaptando a análise ao contexto de cada módulo.

Ao final da execução, o plugin consolida os resultados em relatórios JSON e de console, oferecendo descrições detalhadas em linguagem natural e referências precisas às linhas de código em violação, facilitando a remediação imediata.

📊 Relatórios e Visibilidade

O plugin ArchRules Runner foi projetado para integrar-se ao fluxo de trabalho, oferecendo mecanismos de feedback imediatos e acionáveis. Para garantir que as violações de arquitetura sejam facilmente identificadas e corrigidas, o plugin disponibiliza duas opções nativas de relatório:

🖥️ Relatórios em Console e JSON

O plugin consolida os resultados de todos os source sets dentro de um projeto, gerando:

  • Relatório de Console: Ideal para feedback rápido durante o desenvolvimento local ou execução em CI. Ele apresenta as violações de forma organizada e legível diretamente no terminal.
  • Relatório JSON: Focado na integração com portais de desenvolvedores internos ou ferramentas de análise de dados, agregando todos os dados de violação em um formato estruturado para processamento automatizado e análise de tendências de dívida técnica.

🎯 Clareza e Precisão na Identificação de Problemas

Um dos diferenciais do ArchRules é a qualidade do feedback fornecido. Ao contrário de ferramentas de análise estática tradicionais que podem gerar mensagens crípticas, os relatórios do plugin oferecem:




  • Descrições em Inglês Plano: Cada falha é acompanhada de uma explicação clara e direta sobre o motivo da violação, eliminando ambiguidades sobre o que deve ser corrigido.
  • Localização Exata: O relatório aponta o arquivo e a linha exata do código onde a violação ocorreu, permitindo que o desenvolvedor navegue diretamente ao ponto problemático.

Para cenários de customização avançada, é possível criar tarefas personalizadas que leem os arquivos binários gerados pelo plugin ou utilizar o arquivo JSON para alimentar dashboards de monitoramento de dívida técnica, garantindo que a conformidade arquitetural seja visível para toda a organização.

🏗️ Estudo de Caso: Gerenciamento de Ciclo de Vida de APIs

Para resolver o desafio de gerenciar o ciclo de vida de bibliotecas em um ambiente de milhares de repositórios, a Netflix implementou uma estratégia baseada em anotações padronizadas: @Deprecated (da biblioteca padrão Java), @Public (para APIs destinadas ao uso externo) e @Experimental (para novas APIs ainda instáveis). Qualquer API não anotada é tratada implicitamente como “interna”.

🔍 Monitoramento e Governança com ArchRules

O problema central era a falta de visibilidade sobre como os projetos dependentes consumiam essas APIs. Com o ArchRules, os autores de bibliotecas passaram a escrever regras customizadas que detectam o uso indevido dessas anotações, limitadas ao escopo do pacote de sua própria biblioteca. Um exemplo prático de regra implementada é:


ArchRuleDefinition.priority(Priority.MEDIUM)
.noClasses().that(resideOutsideOfPackage(packageName + ".."))
.should()
.dependOnClassesThat(resideInAPackage(packageName + "..").and(are(deprecated())))
.orShould().accessTargetWhere(targetOwner(resideInAPackage(packageName + ".."))
.and(target(is(deprecated())).or(targetOwner(is(deprecated())))))
.allowEmptyShould(true)
.because("Deprecated APIs are subject to removal");

📈 Confiança na Evolução do Código

A integração dessa solução no ecossistema de build da Netflix permite que os autores de bibliotecas identifiquem exatamente quais projetos consumidores estão utilizando APIs obsoletas ou internas. Ao realizar uma mudança que quebra a compatibilidade (breaking change), o desenvolvedor não precisa mais agir às cegas ou temer impactos desconhecidos. O sistema fornece um mapeamento preciso dos consumidores afetados, permitindo uma comunicação proativa ou a coordenação de migrações antes da remoção definitiva do código.

Essa visibilidade transforma a gestão de dívida técnica: em vez de manter código legado indefinidamente por medo de quebrar dependentes, os autores ganham a segurança necessária para evoluir suas APIs, sabendo que possuem os dados exatos para mitigar qualquer impacto negativo no ecossistema de microsserviços da companhia.

📚 Bibliotecas de Regras OSS e Conclusão

Embora a maior eficácia do ArchRules resida na criação de regras personalizadas, disponibilizamos uma série de bibliotecas de regras open source que podem ser utilizadas imediatamente ou servir como referência para implementações próprias:

🚫 Nullability

Estas regras garantem a aplicação correta de anotações de nulidade em Java. Por exemplo, validam se toda classe pública está marcada com @NullMarked do JSpecify. A lógica é inteligente o suficiente para ignorar código Kotlin, que já possui suporte nativo para nulidade.

🛠️ Melhores Práticas para Plugins Gradle

Escrever plugins Gradle pode ser complexo devido à vasta gama de APIs e padrões legados. Estas regras ajudam a garantir que apenas as práticas recomendadas atuais sejam seguidas durante o desenvolvimento de novos plugins.

⏳ Regras Joda / Guava

Estas bibliotecas desencorajam o uso de classes das bibliotecas Joda Time e Guava, incentivando a migração para java.time e para as melhorias incorporadas na biblioteca padrão do Java.

🔐 Regras de Segurança

Auxiliam na mitigação de CVEs ao detectar o uso de APIs conhecidas como vulneráveis. Embora a atualização de dependências seja a estratégia principal, estas regras oferecem uma camada extra de segurança ao impedir o uso de métodos específicos em tempo de compilação quando a atualização imediata não é viável.

🏁 Conclusão e Próximos Passos

Atualmente, executamos mais de 358 regras em mais de 5.000 repositórios, identificando quase 1 milhão de problemas, dos quais cerca de 1.000 são classificados como de alta prioridade. Essa escala permite obter visibilidade imediata sobre o débito técnico crítico em nossa frota de microsserviços.

Para o futuro, estamos explorando a integração de soluções de auto-remediação. Como o ArchUnit fornece informações detalhadas sobre as falhas, pretendemos utilizá-las como sinal de entrada para ferramentas de correção automática, avaliando tanto abordagens determinísticas, como o OpenRewrite, quanto abordagens não-determinísticas, como LLMs. Além disso, investigamos formas de exibir as falhas do ArchRule diretamente no IDE, tratando-as como inspeções nativas para acelerar o ciclo de feedback.


Fonte: netflixtechblog.com
Curadoria e Insights: Redação YTI&W (Developers).



Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Publicado em:Desenvolvimento de Software,DevOps,Engenharia de Software
Fale Conosco
×

Inscreva-se em nossa Newsletter!


Receba nossos lançamentos e artigos em primera mão!