Verificación limpia en Java usando predicados

Imagine que debe usar una API para recuperar datos del personal de la empresa. Ahora, suponiendo que todos estos datos no sigan ningún patrón, la API puede devolver no solo personas, sino también robots, cuentas "virtuales" y fuentes de toda la información irrelevante. Sin reglas: no hay ningún signo para identificar si los datos pertenecen a una persona u otra criatura, y de vez en cuando encontrará otra variante que clasifica los datos como no válidos.

De acuerdo, eso sucedió. Puede usar "regex" para completar la verificación, pero estará codificado, y los clientes siempre confiarán en los cambios y las nuevas implementaciones en el código.

¡Ajá!

En Java, la forma más efectiva y limpia es crear una tabla para contener las reglas que configuran el registro como no válido, leerlo y convertirlo en predicados, y validar dinámicamente cada parte del valor de retorno de la API para clasificar el objeto Válido o inválido.

Muéstrame el código

Este comportamiento se reprodujo en los proyectos disponibles en mi GitHub utilizando los siguientes métodos: Java 11 y Spring Boot.

Representación de objetos

Los datos de la API externa están representados por la clase DTO. La persona definida DTO está representada por la entidad ExclusionRule y persiste como inválida, donde:

  • fieldName es el atributo en la persona que se verificará el DTO. El operador es un operador AND u OR. El comparador es un comparador EQUALS o CONTAINS.ruleValues ​​son valores separados por comas, estos valores harán que fieldName sea inválido.

Interpretar reglas

El recurso data.sql inicializará algunas reglas para esta prueba:

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');

Las reglas anteriores se pueden interpretar como:

  • Si el objeto DTO en el nombre del atributo contiene 1, 2, 3, 4, 5, 6, 7, 8, 9 o 0, el objeto no es válido. Si el objeto DTO humano en el correo electrónico del atributo contiene "@ exclude.me" o "1", el objeto no es válido. Si el objeto DTO humano en el código interno del atributo contiene "a" y "b", el objeto no es válido. Si el objeto DTO en la posición del atributo es igual a "Júpiter" o "Marte", el objeto no es válido.

Usando predicados

Para cada combinación posible de operadores y comparadores, se crea una clase de validación (la regla contiene suma, la regla contiene suma y la regla es igual). Al implementar la interfaz Predicate <T>, esas clases se pueden usar para validar un objeto a través de la simple y elegante llamada de prueba (myFieldValue). Solo necesita cubrir el método de prueba y definir una regla personalizada.

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);
  }

}

La clase ExclusionRuleService es responsable de recuperar las reglas guardadas y convertirlas en predicados de reglas correspondientes y mantenerlas en la lista.

/**
   * 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;

  }

Donde vive la magia

Ahora que todas las "camas" de verificación se han completado, puede usar el método filterAllValid y no es válido para recibir un objeto o una lista y pasarlos a TestPredicate que no es válido. En el último método, obtuvimos el DTO de persona de campo de la clase conforme a las reglas de exclusión y su valor utilizando Reflexiones.

Es importante tener en cuenta que el uso intensivo de la reflexión puede causar problemas de rendimiento, pero en este caso particular, creo que para lograr la flexibilidad de la verificación, se puede sacrificar parte del rendimiento.

La prueba mágica se llama cuando ocurre el método. No se requiere prueba adicional.

/**
   * 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
  }

Pruebame

En ExclusionRulesServiceTests, podemos verificar si la regla se aplica correctamente a los campos del objeto PersonDTO.

@Test
  public void filterAllValidPersonLstNameContainsOr_ok() {

    final PersonDTO person = new PersonDTO();
    person.setName("Daniane P. Gomes");
    person.setEmail("[email protected]");
    person.setInternalCode("DPG001");
    person.setCompany("ACME");
    person.setLocation("BR");

    final PersonDTO person2 = new PersonDTO();
    person2.setName("Dobberius Louis The Free Elf");
    person2.setEmail("[email protected]");
    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);

  }

Conclusión

Al usar API externas, recibiremos datos estructurados incorrectamente. Para verificar su correlación limpiamente, podemos:

  • Cree un repositorio de reglas y represéntelo como Predicate <T> Convierta los datos de respuesta de la API al objeto DTO de la persona Compruebe si cada atributo de la persona D TO es válido solo llamando a la prueba de método

Publicado originalmente en mi página de Medium .

de: https://dev.to//danianepg/clean-validation-in-java-with-predicates-3p2a

Publicado 0 artículos originales · me gusta 0 · visitas 403

Supongo que te gusta

Origin blog.csdn.net/cunxiedian8614/article/details/105690153
Recomendado
Clasificación