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

Чистая проверка в Java с помощью предикатов

Представьте, что вам нужно использовать API для получения данных от сотрудников вашей компании. А теперь представьте себе это… Помеченный как java, предикат, java 11, строка.

Представьте, что вам нужно использовать API для получения данных от сотрудников вашей компании. Теперь представьте, что все эти данные не следуют никакому шаблону, и API может возвращать не только людей, но и роботов, “фантомные” учетные записи и весь источник нерелевантной информации. Нет никаких правил: нет флага для определения того, принадлежат ли данные человеку или какому-либо другому существу, и время от времени вы можете обнаружить другой вариант, который классифицировал бы данные как недействительные.

Что ж, это случилось. Проверка может быть выполнена с помощью “регулярного выражения”, но она будет жестко запрограммирована, и клиент всегда будет зависеть от изменений в коде и новых развертываний.

Ага!

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

Покажи мне код

Это поведение было воспроизведено в проекте, доступном на моем GitHub, с использованием Java 11 и Пружинный Ботинок.

Представление объекта

Данные внешнего API представлены классом Человек, которому . Правила, которые определяют PersonDTO как недопустимые, представляются и сохраняются с помощью правила исключения объектов, где:

  • fieldName – это атрибут на Лицо, которому это будет проверено.
  • оператор – это оператор И или ИЛИ.
  • comparator – это компаратор, РАВНЫЙ или СОДЕРЖАЩИЙ.
  • значения правила – это значения, разделенные запятой, которые сделали бы имя поля недопустимым.

Интерпретировать правила

Ресурс data.sql инициализирует некоторые правила для целей этого теста:

INSERT INTO exclusion_rule(field_name, comparator, operator, rule_values) VALUES('name', 'CONTAINS', 'OR', '1,2,3,4,5,6,7,8,9,0');
INSERT INTO exclusion_rule(field_name, comparator, operator, rule_values) VALUES('email', 'CONTAINS', 'OR','@exclude.me,1');
INSERT INTO exclusion_rule(field_name, comparator, operator, rule_values) VALUES('internalCode', 'CONTAINS', 'AND','a,b');
INSERT INTO exclusion_rule(field_name, comparator, operator, rule_values) VALUES('location', 'EQUALS', 'OR','jupiter,mars');

Приведенные выше правила могут быть истолкованы как:

  • Если атрибут name на PersonDTO объект содержит 1, 2, 3, 4, 5, 6, 7, 8, 9 или 0, объект недействителен.
  • Если атрибут email на PersonDTO объекта содержит “@exclude.me ” или “1”, объект недействителен.
  • Если атрибут внутренний код объекта PersonDTO содержит “a” и “b”, объект является недопустимым.
  • Если атрибут location на PersonDTO object равен “юпитер” или “марс”, объект недействителен.

Использование предикатов

Для каждой возможной комбинации операторов и компараторов был создан класс проверки ( Правило Содержит И , Правило Содержит Или и Правило Равно Или ). Путем реализации интерфейса Предикат эти классы могут быть использованы для проверки объекта с помощью простого и элегантного вызова test(myFieldValue) . Необходимо только перезаписать метод test и определить пользовательское правило.

public class RuleEqualsOr implements Predicate {

  private List exclusionRulesLst;

  public RuleEqualsOr(final List exclusionRulesLst) {
    this.exclusionRulesLst = exclusionRulesLst;
  }

  @Override
  public boolean test(final String fieldValue) {
    return this.exclusionRulesLst.stream().anyMatch(fieldValue::equals);
  }

}

Класс Служба правил исключения отвечает за извлечение сохраненных правил, преобразование их в соответствующие Предикат и сохраняйте их в списке.

/**
   * Retrieve all rules from the database and process it.
   *
   * @return
   */
  private Map> decodeAllRules() {
    // @formatter:off
    return this.validationRuleRepository.findAll()
        .stream()
        .map(this::deconeOneRule)
        .collect(Collectors.toMap(PairDTO::getRule, PairDTO::getPredicate));
    // @formatter:on

  }

  /**
   * According to the rule configuration, create a Predicate.
   *
   * @param validationRule
   * @return
   */
  private PairDTO deconeOneRule(final ExclusionRule validationRule) {

    PairDTO pairDTO = null;
    List values = new ArrayList<>();

    if (validationRule.getRuleValues().contains(",")) {
      values = Arrays.asList(validationRule.getRuleValues().split(","));
    } else {
      values.add(validationRule.getRuleValues());
    }

    if (validationRule.getComparator() == ComparatorEnum.EQUALS && validationRule.getOperator() == OperatorEnum.OR) {
      pairDTO = new PairDTO(validationRule.getFieldName(), new RuleEqualsOr(values));

    } else {

      if (validationRule.getOperator() == OperatorEnum.OR) {
        pairDTO = new PairDTO(validationRule.getFieldName(), new RuleContainsOr(values));
      } else {
        pairDTO = new PairDTO(validationRule.getFieldName(), new RuleContainsAnd(values));
      }

    }

    return pairDTO;

  }

Где живет магия

Теперь, когда вся проверка “кровати” выполнена, можно использовать методы фильтровать Все Допустимые и является недопустимым для получения объекта или списка и передачи их в является недопустимым тестовым предикатом . В этом последнем методе мы получаем поле класса Лицо, которому который соответствует определенному в Правиле исключения

Важно знать, что интенсивное использование отражений может вызвать проблемы с производительностью, но в данной конкретной ситуации я решил, что некоторой производительностью можно пожертвовать для достижения гибкости проверки.

Волшебство происходит, когда вызывается метод test . Никаких дополнительных испытаний не требуется.

/**
   * Retrieve the person's object fields by reflection and test its validity.
   *
   * @param person
   * @param entry
   * @return
   */
  private Boolean isInvalidTestPredicate(final PersonDTO person, final Entry> entry) {

    final Field field = this.reflectionService.getFieldByName(person, entry.getKey());
    final String fieldValue = String.valueOf(this.reflectionService.getFieldValue(person, field));

    return entry.getValue().test(fieldValue);

  }

  /**
   * Verify if a person is invalid if it fails on any determined rule.
   *
   * @param person
   * @return
   */
  public Boolean isInvalid(final PersonDTO person) {
    return exclusionRulesLst.entrySet().stream().anyMatch(e -> this.isInvalidTestPredicate(person, e));
  }

  /**
   * Get only valid objects from a list
   *
   * @param personDTOLst
   * @return
   */
  public List filterAllValid(final List personDTOLst) {
    // @formatter:off
    return personDTOLst.stream()
              .filter(person -> !this.isInvalid(person))
              .collect(Collectors.toList());
    // @formatter:on
  }

Испытать меня

На занятиях Правила исключения Сервисные тесты мы можем проверить, правильно ли применяются правила к полям объекта PersonDTO.

@Test
  public void filterAllValidPersonLstNameContainsOr_ok() {

    final PersonDTO person = new PersonDTO();
    person.setName("Daniane P. Gomes");
    person.setEmail("danianepg@gmail.com");
    person.setInternalCode("DPG001");
    person.setCompany("ACME");
    person.setLocation("BR");

    final PersonDTO person2 = new PersonDTO();
    person2.setName("Dobberius Louis The Free Elf");
    person2.setEmail("dobby@free.com");
    person2.setInternalCode("DLTFE");
    person2.setCompany("Self Employed");
    person2.setLocation("HG");

    final List personLst = new ArrayList<>();
    personLst.add(person);
    personLst.add(person2);

    final List personValidLst = this.exclusionRuleService.filterAllValid(personLst);

    assertEquals(personValidLst.size(), 2);

  }

Вывод

При использовании внешнего API мы можем получать данные, которые не структурированы должным образом. Чтобы проверить его актуальность чистым способом, мы можем:

  • Создайте хранилище правил и представьте их в виде Предиката
  • Преобразуйте данные ответа API в PersonDTO объект
  • Проверьте, соответствует ли каждый атрибут PersonDTO действителен только при вызове метода тест

Первоначально опубликовано на моя средняя страница .

Оригинал: “https://dev.to/danianepg/clean-validation-in-java-with-predicates-3p2a”