0

在 ArchUnit 中,我可以检查 packages .should().beFreeOfCycles()。如何为某些周期指定此规则的例外情况?

例如,给定这些包及其依赖项:

A <-> B <-> C

我怎样才能允许A <-> B,但仍然禁止AB成为任何其他周期的一部分,例如B <-> C

4

2 回答 2

1

冻结 Arch 规则始终是允许某些违规行为但捕获其他违规行为的一种选择。
在你的情况下这可行吗?

于 2021-12-20T11:03:38.530 回答
1

根据Manfred回答,这里有一个似乎可以正常工作的解决方案(在 Kotlin 中实现):

fun test() {
    SlicesRuleDefinition.slices().matching("(com.mypackage.*..)")
        .should()
        // Using an extension method which allows us to specify allowed
        // cycles succinctly:
        .beFreeOfCyclesExcept("com.mypackage.a" to "com.mypackage.b")
        .check(someClasses)
}

fun SlicesShould.beFreeOfCyclesExcept(
    vararg allowed: Pair<String, String>
): ArchRule =
    FreezingArchRule
        // In case you are not familiar with Kotlin:
        // We are in an extension method. 'this' will be substituted with
        // 'SlicesRuleDefinition.slices().matching("(com.mypackage.*..)")
        // .should()';
        .freeze(this.beFreeOfCycles())
        .persistIn(
            // Using a custom ViolationStore instead of the default 
            // TextFileBasedViolationStore so we can configure the
            // allowed violations in code instead of a text file:
            object : ViolationStore {

                override fun initialize(properties: Properties) {}

                override fun contains(rule: ArchRule): Boolean = true

                override fun save(rule: ArchRule, violations: List<String>) {
                    // Doing nothing here because we do not want ArchUnit 
                    // to add any additional allowed violations.
                }

                override fun getViolations(rule: ArchRule): List<String> =
                    allowed
                        // ArchUnit records cycles in the form 
                        // A -> B -> A. I.e., A -> B -> A and 
                        // B -> A -> B are different violations.
                        // We add the reverse cycle to make sure
                        // both directions are allowed:
                        .flatMap { pair -> 
                            listOf(pair, Pair(pair.second, pair.first)) 
                        }
                        // .distinct() is not necessary, but using it is
                        // cleaner because by adding the reverse cycles
                        // we may possibly have added duplicates:
                        .distinct()
                        .map { (sliceA, sliceB) ->
                            // This is a prefix of the format that
                            // ArchUnit uses:
                            "Cycle detected: Slice $sliceA -> \n" +
                                "                Slice $sliceB -> \n" +
                                "                Slice $sliceA\n"
                        }
            }
        )
        // The lines that ArchUnit uses are very specific, including 
        // info about which methods etc. create the cycle. That is 
        // exactly what is desirable when establishing a baseline for 
        // legacy code. But we want to permanently allow certain 
        // cycles, regardless of which current or future code creates 
        // the cycle. Thus, we only compare the prefixes of violation
        // lines:
        .associateViolationLinesVia { 
            lineFromFirstViolation,
            lineFromSecondViolation ->
                lineFromFirstViolation.startsWith(lineFromSecondViolation)
        }

请注意,我仅在一个小项目上对此进行了测试。

于 2021-12-20T14:44:10.377 回答