【问题标题】:How to allow only certain cyclic dependencies in ArchUnit?如何在 ArchUnit 中只允许某些循环依赖?
【发布时间】:2021-12-20 10:29:18
【问题描述】:

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

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

A <-> B <-> C

我怎样才能允许A &lt;-&gt; B,但仍然禁止AB 成为任何其他循环的一部分,例如B &lt;-&gt; C?

【问题讨论】:

    标签: archunit


    【解决方案1】:

    Freezing Arch Rules 始终是允许某些违规行为但捕获其他违规行为的选项。
    在你的情况下这可行吗?

    【讨论】:

    • 谢谢。这不方便,但似乎工作。我已将我的代码发布为another answer
    【解决方案2】:

    基于Manfredanswer,这是一个似乎工作正常的解决方案(在 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)
            }
    

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

    【讨论】:

    • 我的建议是用 FreezingArchRule.freeze - freeze( slices().matching("(com.mypackage.*..)").should().beFreeOfCycles() ) 包装你的规则 - 并将允许的循环放在 TextFileBasedViolationStore 中。
    • 使用默认的TextFileBasedViolationStore 而不是自定义的ViolationStore 会起作用。但是,您需要手动输入详细的 Cycle detected: Slice com.mypackage.a -&gt; (...) (以及另外 - 以防万一 - 也是反转方向的线),而不是从简洁的表示 "com.mypackage.a" to "com.mypackage.b" 生成实际违规行文件。
    • 为避免混淆:您的freeze(slices().matching("(com.mypackage.*..)").should().beFreeOfCycles()) 正是在这里发生的。我只是没有对SlicesRuleDefinition.slices() 使用静态导入,而在我的扩展方法beFreeOfCyclesExcept() 中,this 将被SlicesRuleDefinition.slices().matching("(com.mypackage.*..)").should() 替换。
    • 对。我只是想说我会使用默认的TextFileBasedViolationStore。您不必手动添加任何行; ArchUnit 可以configured 在第一次测试执行期间设置存储(freeze.store.default.allowStoreCreation=true)。
    • 确实如此。当然,您不能提前设置任何允许的周期。并且将来 ArchUnit 也有可能检测到反向循环,然后将其视为不同的违规(A -> B -> A vs. B -> A -> B)。
    猜你喜欢
    • 2011-07-22
    • 1970-01-01
    • 2021-10-09
    • 1970-01-01
    • 1970-01-01
    • 2019-06-15
    • 1970-01-01
    • 2021-01-28
    • 1970-01-01
    相关资源
    最近更新 更多