想象一下,您必须使用API才能从公司人员那里检索数据。 现在,假设所有这些数据都不遵循任何模式,API不仅可以返回人员,还可以返回机器人,“虚拟”帐户以及所有不相关信息的来源。 没有规则:没有标志来标识数据是属于一个人还是其他生物,并且您会不时发现另一种将数据分类为无效的变体。
好吧,那件事发生了。 可以使用“ regex”来完成验证,但是将对其进行硬编码,并且客户将始终依赖于代码中的更改和新部署。
在Java中,最有效,最干净的方法是创建一个表,以保存将记录配置为无效的规则,将其读取并转换为谓词,并动态验证API返回值的每个部分以将对象分类为 有效或无效。
使用以下方法在我的GitHub上可用的项目中重现了此行为:Java 11和Spring Boot。
外部API的数据由类表示人DTO。 定义人DTO通过实体ExclusionRule表示并持久化为无效,其中:
fieldName是上的属性人DTO将被检查。算子 is an 算子 AND or OR.比较器 is a 比较器 EQUALS or CONTAINS.ruleValues是用逗号分隔的值,这些值会使fieldName无效。
Interpret rules
资源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');
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');
上面的规则可以解释为:
- 如果属性名称上人DTO对象包含1、2、3、4、5、6、7、8、9或0,则该对象无效。如果属性电子邮件上人DTO对象包含“ @ exclude.me”或“ 1”,则该对象无效。如果属性内部代码上人DTO对象包含“ a”和“ b”,则该对象无效。如果属性位置上人DTO该对象等于“木星”或“火星”,则该对象无效。
Using Predicates
对于运算符和比较器的每种可能组合,都会创建一个验证类(规则包含和,规则包含或和规则等于)。 通过实现接口Predicate<T> those classes can be used to validate an object through the simple和elegant call of 测试(myFieldValue)。 只需要覆盖测试 method和define a custom rule.
public class RuleEqualsOr implements Predicate<String> {
private List<String> exclusionRulesLst;
public RuleEqualsOr(final List<String> exclusionRulesLst) {
this.exclusionRulesLst = exclusionRulesLst;
}
@Override
public boolean test(final String fieldValue) {
return this.exclusionRulesLst.stream().anyMatch(fieldValue::equals);
}
}
public class RuleEqualsOr implements Predicate<String> {
private List<String> exclusionRulesLst;
public RuleEqualsOr(final List<String> exclusionRulesLst) {
this.exclusionRulesLst = exclusionRulesLst;
}
@Override
public boolean test(final String fieldValue) {
return this.exclusionRulesLst.stream().anyMatch(fieldValue::equals);
}
}
类ExclusionRuleService负责检索保存的规则,并将其转换为相应的规则谓语并将它们保留在列表中。
/**
* Retrieve all rules from the database and process it.
*
* @return
*/
private Map<String, Predicate<String>> 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<String> 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;
}
/**
* Retrieve all rules from the database and process it.
*
* @return
*/
private Map<String, Predicate<String>> 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<String> 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;
}
Where the magic lives
现在,所有验证“床”均已完成,可以使用方法filterAllValid和是无效的 to receive an object or a list和pass them to 是无效的TestPredicate。 在最后一个方法上,我们获得了类的字段人DTO符合定义于排除规则和its value using Reflections.
请务必注意,大量使用反射会导致性能问题,但是在这种特殊情况下,我认为为了实现验证的灵活性,可能会牺牲一些性能。
当方法发生魔术测试is called. No additional 测试 is required.
/**
* 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<String, Predicate<String>> 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<PersonDTO> filterAllValid(final List<PersonDTO> personDTOLst) {
// @formatter:off
return personDTOLst.stream()
.filter(person -> !this.isInvalid(person))
.collect(Collectors.toList());
// @formatter:on
}
/**
* 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<String, Predicate<String>> 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<PersonDTO> filterAllValid(final List<PersonDTO> personDTOLst) {
// @formatter:off
return personDTOLst.stream()
.filter(person -> !this.isInvalid(person))
.collect(Collectors.toList());
// @formatter:on
}
Test me
上课ExclusionRulesServiceTests我们可以检查规则是否正确地应用于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<PersonDTO> personLst = new ArrayList<>();
personLst.add(person);
personLst.add(person2);
final List<PersonDTO> personValidLst = this.exclusionRuleService.filterAllValid(personLst);
assertEquals(personValidLst.size(), 2);
}
@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<PersonDTO> personLst = new ArrayList<>();
personLst.add(person);
personLst.add(person2);
final List<PersonDTO> personValidLst = this.exclusionRuleService.filterAllValid(personLst);
assertEquals(personValidLst.size(), 2);
}
Conclusion
使用外部API时,我们会收到结构不正确的数据。 为了干净地检查其相关性,我们可以:
- 创建规则存储库并将其表示为Predicate<T>ConverttheAPIresponsedatatoa人DTOobjectCheckifeachattributeof人DTOisvalidonlybycallingthemethod测试
Originally posted on my Medium page.