netflix4 min read

Curated summary

Scaling ArchUnit with Nebula ArchRules

Read original(opens in new tab)

Netflix’s Nebula ArchRules extends ArchUnit so architectural and API-lifecycle rules can be shared across thousands of Gradle repositories. Unlike AST-based tools, ArchUnit analyzes compiled JVM bytecode, supports multiple JVM languages, and offers a type-safe Java API for authoring and testing rules. The approach helps identify unsafe API usage, technical debt, and deviations from Netflix’s preferred development practices at fleet scale.

The API Lifecycle Problem

  • Netflix operates tens of thousands of Java repositories in a polyrepo environment.
  • A library incident involving a backwards-incompatible change highlighted the difficulty of deciding when deprecated APIs can safely be removed.
  • Netflix introduced lifecycle annotations:
    • @Deprecated for APIs scheduled for removal
    • @Public for APIs intended for downstream use
    • @Experimental for APIs that may change
    • Unannotated APIs are treated as internal
  • The remaining challenge was identifying downstream projects that use internal, experimental, or deprecated APIs incorrectly.
  • The same tooling could support large migrations, such as major Spring Boot upgrades.

Why ArchUnit

  • ArchUnit is an open-source library commonly used within JUnit suites to enforce architectural rules.
  • It is built on ASM and analyzes compiled JVM bytecode rather than source syntax.
  • Its main strengths are:
    • Cross-language JVM support for Java, Kotlin, Scala, and other JVM languages
    • A fluent builder API for readable rule definitions
    • A lower-level API for complex custom analysis
    • Access to class relationships, dependencies, and call sites through its class graph
  • Standard ArchUnit is primarily designed for one repository, so Netflix created Nebula ArchRules to distribute rules across many Gradle projects.

Bytecode Analysis vs. AST Analysis

  • AST-based tools such as PMD inspect source-code structure and can be sensitive to language-specific syntax and syntactic sugar.
  • Supporting multiple JVM languages may require separate rules for each language.
  • ASM analyzes the bytecode that will actually execute, regardless of how the source was written.
  • This makes rules more consistent across Java, Kotlin, Scala, and other JVM languages.

Rule Authoring

  • Tools such as PMD and SpotBugs are generally optimized for built-in rules or third-party plugins rather than custom rule development.
  • PMD custom rules may require difficult-to-maintain XPath expressions and separate tooling for testing.
  • ArchUnit rules are written as type-safe, fluent Java code.
  • Rules can be unit tested directly by passing them class references, without running a separate analysis process.
  • ArchUnit’s class graph provides contextual information about dependencies and call relationships, enabling more sophisticated checks.

ArchRules Libraries

  • The Nebula ArchRules Library Plugin adds an archRules source set to a Gradle project.
  • A class implementing ArchRulesService exposes a Map<String, ArchRule>:
    • The map key names the rule.
    • The ArchRule defines the constraint using ArchUnit’s API.
  • Rule code and its dependencies are kept separate from the application’s main code.
  • Gradle publishes the rules in a separate JAR using the arch-rules classifier and an arch-rules usage attribute.
  • Downstream projects must use Gradle Module Metadata to resolve the rules variant.

Standalone and Bundled Rule Libraries

  • Standalone rule libraries contain only archRules code.
  • They are useful for:
    • Enforcing rules around APIs the organization does not own
    • Checking usage of Java or open-source libraries
    • Applying generic rules, such as prohibiting use of deprecated APIs
  • Bundled rule libraries contain both normal library code and rules specific to how that library should be used.
  • Netflix maintains open-source standalone rule libraries as examples and reusable building blocks.

Nebula ArchRules turns ArchUnit from a repository-local testing library into a reusable organization-wide policy mechanism. Teams can publish rules as Gradle artifacts and apply them consistently across JVM projects, making API governance, dependency policies, and architectural standards easier to enforce at scale.

Continue with another curated summary.