Рубрики
Без рубрики

Гарантия и обеспечение вашей архитектуры (с помощью Arch Unit)

Соблюдение архитектурных правил с помощью кода. Помеченный java, архитектура.

Недавно я работал над приложением, которое в значительной степени опиралось на различные источники данных. Наше приложение фактически объединяет, интерпретирует и визуализирует данные.

Не всем разрешено видеть все данные. Нам нужен был простой способ защитить доступ к нашим ресурсам, хотя и не только на основе типичных пользователей и ролей. Доступ к данным должен быть защищен на более детализированном уровне. Однако, хотя, с одной стороны, правила доступа определяются набором взаимоисключающих параметров, мы также хотели сохранить код как можно более чистым и доступным для обслуживания.

Поскольку наше приложение уже полагалось на Spring Boot, наш выбор пал на spring-безопасность, а точнее, на аннотацию @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();
    }
}

Хорошо. Казалось бы, проблема решена…

Это верно лишь отчасти. Хорошо, мы изолировали логику, которая предоставляет доступ к методу с учетом определенного параметра метода, но мы ввели другие менее заметные проблемы.

Предположим, к команде присоединяется новый разработчик. И он видит эти @Конфиденциальные аннотации, защищающие доступ к ресурсам. И он применяет их на:

@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.

Итак, как нам решить эту проблему? Хранить обширный список правил в вики и позволять всем просматривать его? Настроить правило сонара и собирать отчеты каждую ночь? Нет, о чем… внедрять эти правила в быстро выполняемый модульный тест и получать немедленную обратную связь?

Пожалуйста, добро пожаловать Арочный Блок

Модуль Arch: Библиотека тестов архитектуры Java, предназначенная для простого определения и утверждения правил архитектуры Ява Итак, давайте углубимся и напишем тест, который обеспечит правильное использование нашей аннотации.

public class SensitiveAnnotationUsageTest {

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

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

                    for(JavaMethod method: collect){

                        List 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

И вот! У нас есть тест, который обеспечивает правильное применение наших архитектурных правил.

Любопытно узнать больше о подразделении Arch? Обязательно ознакомьтесь с их руководством пользователя и примерами .

Оригинал: “https://dev.to/davidcyp_52/guaranteeing-and-enforcing-your-architecture-with-archunit–4nf5”