feat(arch-guard): enforce architecture rules in FAIL mode + dedicated CI workflow (#5305) - #5322
Merged
qqeasonchen merged 4 commits intoSep 1, 2026
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
LoggerFactoryreturns the NOP logger and everycall was discarded. Verified empirically: the
architectureChecktest-results XML has an empty<system-out>block even though all 10*_warnmethods 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.javaexcluded"org.apache.eventmesh.protocol.plugin.meshmessage.."but the real package is
org.apache.eventmesh.protocol.meshmessage- there is no.pluginsegment. Confirmed withgit cat-file -p ... | grep ^package. The exclusion never matched, so all 7 protocol adapters were judged as ordinary offenders:ruleHttpProtocolHiddenruleTcpProtocolHiddenruleGrpcProtocolHiddenFixed by correcting the three package strings. No rule semantics change.
Defect 2 -
ruleRuntimeTcpInternalNoReversewas unsatisfiable (25 violations)The trailing
..on the should() side coversruntime.tcp.internalitself, so the rule forbids internal classes from depending on their own package. Every reported "violation" was of this shape: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 byruleRuntimeTcpInternalHidden, which keeps everything outsideruntime.tcpaway fromruntime.tcp.internal.Rules in force: 10 -> 9. All 9 pass in FAIL mode.
A+B
B - local (unchanged mechanism, now actually effective):
Fails in seconds, so a developer sees a violation before pushing.
A - CI (new):
.github/workflows/architecture-guard.ymlruns 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::errorannotation, no auto PR comment - matching howci.ymlandlicense.ymlalready behave.ci.ymldrops:eventmesh-architecture-guard:testfrom the matrix build so the rules do not run twice.Commits
5aab8a64feat(arch-guard): enforce rules via rule.check(classes) - WARN -> FAIL, drop Logger/EvaluationResult imports, LOG field, stale Javadoce9115ee4fix(arch-guard): correct meshmessage package in protocol-hidden rules - 3 package strings3b1fa6f9fix(arch-guard): drop broken ruleRuntimeTcpInternalNoReverse - rule + test59a992e1ci(arch-guard): dedicated Architecture Guard workflow - new workflow, ci.yml exclude, READMEVerification
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).