Skip to main content
版本:v2.0.0

EventMesh Architecture Guard

Issue: #5305 (P0), #5322 (fail-mode), #5342 (Q7 expansion)

This document is the canonical reference for the eventmesh-architecture-guard module. It explains every ArchUnit rule, its rationale, and how to add a new one.

If you are a contributor opening a PR that touches one of the analysed modules, read this first -- it tells you which boundaries you must respect and what to expect if you cross one.

How it works

The eventmesh-architecture-guard Gradle module runs an ArchUnit analysis across the production classpath on every build (and in CI). Every rule is asserted in FAIL mode (rule.check(classes) throws on violation), so a boundary break fails the build.

The CI workflow at .github/workflows/architecture-guard.yml runs the analysis as its own check on every PR that touches the analysed modules, so a violation appears as "Architecture Guard" on the PR rather than buried in the 30-minute Build job log.

The list of analysed modules (i.e. what triggers the workflow and what shows up in the classpath for loadProductionClasses()):

ModuleReason for analysis
eventmesh-commonsource of common.internal.., common.protocol.*
eventmesh-runtimesource of runtime.tcp.internal.., etc.
eventmesh-spiguaranteed "outside" package for that-clauses
eventmesh-protocol-pluginmeshmessage / SDKs / protocol sub-packages
eventmesh-storage-plugin (kafka only)the storage plugin canary for ruleStoragePlugins*
eventmesh-connector-apiconnector SPI boundary
eventmesh-connector-runtimeruntime classes named in ruleConnector*
eventmesh-connector-plugin:eventmesh-connector-filethe connector plugin canary for ruleConnector*

Severity: FAIL

Earlier versions used a *_warn rule shape that logged violations to SLF4J but never failed. That mode was silent because the test module has no SLF4J binding on its runtime classpath -- LoggerFactory hands back the NOP logger and every LOG.warn(report) was discarded. All rules now use rule.check(classes) in the JUnit test, which throws AssertionError with a per-class violation report.

Rules (12 total)

eventmesh-common boundaries (5)

#RuleForbids
1ruleInternalHiddenorg.apache.eventmesh.common.internal.. reached from outside org.apache.eventmesh.common..
2ruleHttpProtocolHiddenorg.apache.eventmesh.common.protocol.http.. from modules other than meshmessage / sdks
3ruleGrpcProtocolHiddenorg.apache.eventmesh.common.protocol.grpc.. from modules other than meshmessage / sdks
4ruleTcpProtocolHiddenorg.apache.eventmesh.common.protocol.tcp.. from modules other than meshmessage / sdks / runtime
5ruleOldUtilsRenamedorg.apache.eventmesh.common.utils.. (renamed to .util.. in #5298)

eventmesh-runtime boundaries (4)

#RuleForbids
6ruleRuntimeTcpInternalHiddenruntime.tcp.internal.. reached from outside runtime.tcp..
7ruleRuntimeEngineIsolatedFromInfraruntime.boot / ingress / delivery depending on runtime.tcp.internal..
8ruleRuntimePushDoesNotImportCodecruntime.push depending on runtime.tcp.internal..
9ruleRuntimeSubscriptionStateIsolatedruntime.ingress depending on runtime.state.internal..

Plugin SPI boundaries (3)

#RuleForbids
10ruleConnectorPluginsDependOnlyOnSpiorg.apache.eventmesh.connector.<plugin>.. depending on connector-runtime internals by name
11ruleStoragePluginsIsolatedone storage plugin's package reaching into another storage plugin's package
12ruleStoragePluginsDependOnlyOnApia storage plugin depending on eventmesh.runtime.. or eventmesh.connector.runtime..

How to add a new rule

  1. Open ArchitectureRules.java in eventmesh-architecture-guard/src/main/java/org/apache/eventmesh/architecture/guard/.
  2. Add a public static ArchRule ruleXxx = noClasses()... (or classes()...) field. End the chain with .because("...") so the violation report tells the reader what to fix.
  3. Add a void ruleXxx_check() { ArchitectureRules.ruleXxx.check(classes); } method in ArchitectureRulesTest. The test method MUST be named *_check (the B mode that fails on violation).
  4. If your rule targets a module not yet in the testImplementation list of eventmesh-architecture-guard/build.gradle, add it.
  5. Update the rule table in this document.
  6. Run ./gradlew :eventmesh-architecture-guard:architectureCheck to confirm your rule loads and the test passes on a clean tree.

Running locally

./gradlew :eventmesh-architecture-guard:architectureCheck
# or, for the JUnit form (FAIL mode):
./gradlew :eventmesh-architecture-guard:test

CI

.github/workflows/architecture-guard.yml runs the same task as its own check on pushes and pull requests that touch the analysed modules. The matrix build in ci.yml excludes :eventmesh-architecture-guard:test to avoid running the rules twice.

Intentional-violation proof (issue #5342 acceptance)

Issue #5342 asks for "an intentional-violation PR that proves the guard catches violations." We achieve this without a separate PR by shipping two canary classes in src/test/java/:

  • org.apache.eventmesh.connector.fakeplugin.FakePluginCanary (introduced in PR #5328) -- reaches into org.apache.eventmesh.connector.ConnectorRuntime. The companion ArchitectureRulesTest.ruleConnectorPluginsDependOnlyOnSpiCatchesViolations asserts the rule fails with the canary class named in the report.
  • org.apache.eventmesh.storage.fakeplugin.FakeStorageCanary (introduced in this issue) -- reaches into org.apache.eventmesh.storage.rocketmq5.storage. The companion ruleStoragePluginsIsolated_catches test asserts the same shape for the storage plugin boundary.

These canaries live in src/test/java/ so production code stays clean, and the production rule (DO_NOT_INCLUDE_TESTS) is exercised by a focused unit test that re-imports the canary package. This is the same pattern ArchUnit's own examples use for "prove the rule works."

Further reading

  • docs/feature/storage-spi.md -- the storage capability matrix
  • eventmesh-architecture-guard/README.md -- module-level summary
  • CONTRIBUTING.md -- how to add a new module