使用 ArchUnit 来保证和加强你的架构

本文介绍如何利用ArchUnit库来确保和加强基于Spring Boot的应用的架构。在面临复杂的数据访问规则时,通过创建自定义注解避免了代码维护问题,但同时也引入了隐性规则。为了解决这个问题,文章提出了使用ArchUnit来编写单元测试,验证方法上的注解使用是否符合体系结构规则,提供即时反馈,增强代码质量。
摘要由CSDN通过智能技术生成

Guaranteeing and enforcing your architecture (with ArchUnit)

最近,我正在开发一个严重依赖各种数据源的应用程序。 我们的应用程序实际上是对数据进行组合、解释和可视化。

并非所有人都可以查看所有数据。 我们需要一种简单的方法来保护对我们资源的访问,尽管不仅仅基于典型的用户和角色。 应当更细化地保护对数据的访问。 但是,一方面访问规则是由一组互斥的参数定义的,但我们也希望保持代码尽可能的干净和可维护。

由于我们的应用程序已经依赖于Spring Boot,因此我们的选择取决于spring-security,更确切地说,它是@PreAuthorize批注。

我们本可以选择复制/粘贴/适应随附的SPeL表达式,尽管这样会很快引入维护问题,例如, 假设我们只有2个不同的参数用于授予对一条数据的访问权限,那么我们仍将在整个代码中复制SPeL表达式,每当访问规则发生更改时都会引入维护问题。


   @Component
   public class SomeSecuredResource {

    @PreAuthorize("hasAccess(#foo)")
    public SensitiveData showMe(Foo foo){
        return new SensitiveData();
    }

    @PreAuthorize("isAllowedToSee(#bar)")
    public SensitiveData showMeToo(Bar bar){
        return new SensitiveData();
    }
   }

所以我们为此创建了一个简单的注释


@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@PreAuthorize(Sensitive.EXPRESSION)

public @interface Sensitive {

    String EXPRESSION = "#foo != null ? hasAccess(#foo) : (#bar != null ? isAllowedToSee(#bar) : false)";

}

我们现在可以应用:


@Component
public class SomeSecuredResource {

    @Sensitivepublic SensitiveData showMe(Foo foo){
        return new SensitiveData();
    }

    @Sensitivepublic SensitiveData showMeToo(Bar bar){
        return new SensitiveData();
    }
}

很好。问题解决了,人们会认为……

这只是部分正确。 好的,我们隔离了在给定特定方法参数的情况下允许访问方法的逻辑,但是引入了其他不那么明显的问题。

假设有新的开发人员加入团队。 而且他看到了这些@Sensitive批注保护了对资源的访问。 他将它们应用于:


@Component
public class NewDevsClass {

    @Sensitivepublic SensitiveData doSomething(long someParameter){
       return new SensitiveData(); 
    }

    @Sensitivepublic SensitiveData doOtherStuff(String foo){
        return new SensitiveData();
    }
}

他在这里违反了隐性规则。 如我们所见,@ Sensitive批注的实现依赖于Foo或Bar类型的参数。

实际上,他违反了我们的体系结构规则,即:

用@Sensitive注释注释的方法必须具有Foo或Bar类型的参数。

那么,我们该如何解决呢? 在Wiki上保留大量的规则列表,并让每个人都对其进行筛选吗? 配置Sonar规则并每晚收集报告? 否,将这些规则嵌入快速执行的单元测试中并获得即时反馈会怎么样?

请大家欢迎 ArchUnit

ArchUnit:一个Java体系结构测试库,用于以纯Java指定和声明体系结构规则。因此,让我们深入研究并编写测试来确保正确使用我们的注释。


public class SensitiveAnnotationUsageTest {

    DescribedPredicate<JavaClass> haveAFieldAnnotatedWithSensitive =
            new DescribedPredicate<JavaClass>("have a field annotated with @Sensitive"){
                @Overridepublic boolean apply(JavaClass input) {
                    // note : simplified version which inspects all classesreturn true;
                }
            };

    ArchCondition<JavaClass> mustContainAParameterOfTypeFooOrBar =
            new ArchCondition<JavaClass>("must have parameter of type 'com.example.post.Foo' or 'com.example.post.Bar'") {
                @Overridepublic void check(JavaClass item, ConditionEvents events) {
                    List<JavaMethod> collect = item.getMethods().stream()
                            .filter(method -> method.isAnnotatedWith(Sensitive.class)).collect(Collectors.toList());

                    for(JavaMethod method: collect){

                        List<String> names = method.getParameters().getNames();

                        if(!names.contains("com.example.post.Foo") && !names.contains("com.example.post.Bar"))  {
                            String message = String.format(
                                    "Method %s bevat geen parameter met type 'Foo' of 'Bar", method.getFullName());
                            events.add(SimpleConditionEvent.violated(method, message));
                        }
                    }
                }
            };

    @Testpublic void checkArchitecturalRules(){
        JavaClasses importedClasses = new ClassFileImporter().importPackages("com.example.post");

        ArchRule rule = ArchRuleDefinition.classes()
                .that(haveAFieldAnnotatedWithSensitive)
                .should(mustContainAParameterOfTypeFooOrBar);


        rule.check(importedClasses);
    }
}

在我们的2个类上执行此测试将得出以下结果:


java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'classes that have a field annotated with @Sensitive should must have parameter of type 'com.example.post.Foo' was violated (2 times):
Method com.example.post.NewDevsClass.doOtherStuff(java.lang.String) bevat geen parameter met type 'Foo' of 'Bar
Method com.example.post.NewDevsClass.doSomething(long) bevat geen parameter met type 'Foo' of 'Bar

看! 我们已经进行了测试,并且可以正确应用我们的架构规则。

是否想了解有关ArchUnit的更多信息?请务必查看其用户指南示例

原文链接: https://dev.to//davidcyp_52/guaranteeing-and-enforcing-your-architecture-with-archunit--4nf5

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值