Представьте, что вам нужно использовать 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 ListpersonLst = 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”