Skip to content

feat(arch-guard): enforce architecture rules in FAIL mode + dedicated CI workflow (#5305) - #5322

Merged
qqeasonchen merged 4 commits into
apache:developfrom
qqeasonchen:feat/5305-arch-guard-fail-mode
Sep 1, 2026
Merged

feat(arch-guard): enforce architecture rules in FAIL mode + dedicated CI workflow (#5305)#5322
qqeasonchen merged 4 commits into
apache:developfrom
qqeasonchen:feat/5305-arch-guard-fail-mode

Conversation

@qqeasonchen

@qqeasonchen qqeasonchen commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Completes the A+B setup for :eventmesh-architecture-guard (issue #5305) and, in the process, fixes two defects that made the rules useless since they landed in #5298 / #5297.

The headline problem: WARN mode was silent

The module has no SLF4J binding on its test runtime classpath, so LoggerFactory returns the NOP logger and every

LOG.warn("...violations:/n{}", r.getFailureReport());

call was discarded. Verified empirically: the architectureCheck test-results XML has an empty <system-out> block even though all 10 *_warn methods ran and passed.

The 10 rules had never actually been read by anyone. CI green was not evidence of anything.

What FAIL mode revealed

Switching to rule.check(classes) immediately reported 192 violations across 4 rules. Two defects accounted for all of them.

Defect 1 - wrong package name in 3 exclusion clauses (167 violations)

ArchitectureRules.java excluded

"org.apache.eventmesh.protocol.plugin.meshmessage.."

but the real package is org.apache.eventmesh.protocol.meshmessage - there is no .plugin segment. Confirmed with git cat-file -p ... | grep ^package. The exclusion never matched, so all 7 protocol adapters were judged as ordinary offenders:

Rule Violations
ruleHttpProtocolHidden 107
ruleTcpProtocolHidden 46
ruleGrpcProtocolHidden 14

Fixed by correcting the three package strings. No rule semantics change.

Defect 2 - ruleRuntimeTcpInternalNoReverse was unsatisfiable (25 violations)

.that().resideInAPackage("...tcp.internal..")
.should().dependOnClassesThat().resideInAPackage("org.apache.eventmesh.runtime.tcp..")

The trailing .. on the should() side covers runtime.tcp.internal itself, so the rule forbids internal classes from depending on their own package. Every reported "violation" was of this shape:

NettyTcpPushChannel.deliver -> TcpAckRegistry.register              internal -> public (normal)
TcpPushChannel.deliver      -> TcpFrameCodec.encodePush             internal -> internal
TcpPushChannel.deliver      -> TcpPushChannel$TcpSessionSink.write  its own nested type
TcpRequest$Kind.values      -> TcpRequest$Kind.clone                itself

Rule deleted. Narrowing the .. would not rescue it either: internal implementations depending on the public types they implement is ordinary layering. The boundary that matters is already covered by ruleRuntimeTcpInternalHidden, which keeps everything outside runtime.tcp away from runtime.tcp.internal.

Rules in force: 10 -> 9. All 9 pass in FAIL mode.

A+B

B - local (unchanged mechanism, now actually effective):

./gradlew :eventmesh-architecture-guard:architectureCheck

Fails in seconds, so a developer sees a violation before pushing.

A - CI (new): .github/workflows/architecture-guard.yml runs the same task on pushes/PRs touching the analysed modules. A violation surfaces as its own "Architecture Guard" check instead of hiding in the 30-minute Build job. Failure reporting uses the gradle exit code only - no ::error annotation, no auto PR comment - matching how ci.yml and license.yml already behave.

ci.yml drops :eventmesh-architecture-guard:test from the matrix build so the rules do not run twice.

Commits

  1. 5aab8a64 feat(arch-guard): enforce rules via rule.check(classes) - WARN -> FAIL, drop Logger/EvaluationResult imports, LOG field, stale Javadoc
  2. e9115ee4 fix(arch-guard): correct meshmessage package in protocol-hidden rules - 3 package strings
  3. 3b1fa6f9 fix(arch-guard): drop broken ruleRuntimeTcpInternalNoReverse - rule + test
  4. 59a992e1 ci(arch-guard): dedicated Architecture Guard workflow - new workflow, ci.yml exclude, README

Verification

./gradlew :eventmesh-architecture-guard:check

Local: BUILD SUCCESSFUL - 9/9 rules pass in FAIL mode, plus compileJava/compileTestJava, checkstyle, pmd, spotbugs all clean. Both workflow YAMLs parse cleanly.

Note for reviewers

Because WARN mode never emitted anything, no violation list was ever published, so this PR is the first time these rules have actually run. If any team believes a specific rule is too strict, that is now a visible, discussable failure rather than a silent one.

Refs: #5305. Builds on #5298 (PR #5320) and #5297 (PR #5321).

Switches ArchitectureRulesTest from WARN mode to FAIL mode.

WARN mode was silently useless. The arch-guard module has no SLF4J
binding on its test runtime classpath, so LoggerFactory returns the
NOP logger and every `LOG.warn(r.getFailureReport())` call is
discarded. Verified empirically: the architectureCheck test-results
XML contains an empty <system-out> block even though all 10 *_warn
methods ran and passed.

With `rule.check(classes)`, a violation throws AssertionError carrying
the rule description and every violating location, so the build itself
reports the real state of the codebase. This is the API the official
ArchUnit examples use and it does not depend on logging at all.

Also drops the now-unused Logger/LoggerFactory/EvaluationResult
imports, the LOG field, and the stale "From 1.14.0" Javadoc paragraph.
…apache#5305)

The three protocol-hidden rules (http / grpc / tcp) excluded
"org.apache.eventmesh.protocol.plugin.meshmessage..", but the real
package is "org.apache.eventmesh.protocol.meshmessage" -- there is no
".plugin" segment.

Because the exclusion never matched, all 7 protocol adapter classes
were judged as ordinary offenders. In FAIL mode that produced 167 of
the 192 reported violations:

  ruleHttpProtocolHidden   107
  ruleTcpProtocolHidden     46
  ruleGrpcProtocolHidden    14

No rule semantics change; only the three package strings are corrected.
…5305)

The rule forbade classes in runtime.tcp.internal from depending on
runtime.tcp.., but the trailing ".." covers runtime.tcp.internal
itself. The result was that a class depending on its own nested type
counted as an architecture violation. All 25 reported violations were
of this shape:

  NettyTcpPushChannel.deliver -> TcpAckRegistry.register   (internal -> public, normal)
  TcpPushChannel.deliver      -> TcpFrameCodec.encodePush  (internal -> internal)
  TcpPushChannel.deliver      -> TcpPushChannel$TcpSessionSink.write (own nested type)
  TcpRequest$Kind.values      -> TcpRequest$Kind.clone     (itself)

Narrowing the ".." would not rescue the rule either: internal
implementation classes depending on the public types they implement is
ordinary layering, not a violation.

The boundary worth enforcing is already covered by
ruleRuntimeTcpInternalHidden, which keeps everything outside
runtime.tcp away from runtime.tcp.internal. That rule stays.

Rules in force drop from 10 to 9; all 9 pass in FAIL mode.
Completes the A+B setup for the architecture rules.

B (local): :eventmesh-architecture-guard:architectureCheck already
fails the build on violation, so a developer gets feedback in seconds
without waiting for CI.

A (CI): .github/workflows/architecture-guard.yml runs the same task on
pushes and pull requests touching the analysed modules. A violation now
surfaces as its own "Architecture Guard" check instead of hiding inside
the 30-minute Build job, and it finishes in a fraction of the time
because it only compiles what the rules need.

ci.yml drops :eventmesh-architecture-guard:test from the matrix build
so the rules are not executed twice.

Failure reporting uses the gradle exit code only - no ::error
annotation and no auto PR comment - matching how ci.yml and license.yml
already behave.

README is updated to the 9 rules actually in force (the 5 common rules
from apache#5298 plus the 4 runtime rules from apache#5297) and documents why WARN
mode was replaced: the module has no SLF4J binding, so the NOP logger
silently discarded every violation report.
@qqeasonchen
qqeasonchen merged commit 6e92e2d into apache:develop Sep 1, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant