¿Cómo validar un campo en una arquitectura de microservicios? 🤔

En el post de hoy hablaremos acerca de Exploración de patrones prácticos de desarrollo de software moderno, como la alternancia de funciones y las versiones "canary".

· 6 min de lectura
¿Cómo validar un campo en una arquitectura de microservicios? 🤔

Si llevas unos días dedicándote al desarrollo web, lo más probable es que hayas oído hablar de la computación en nube y los microservicios.

A diferencia de las aplicaciones monolíticas, que se construyen como unidades únicas y grandes, en una arquitectura de microservicios descomponemos una aplicación grande en un conjunto de piezas pequeñas e independientes que podemos desplegar y escalar por separado.

Si aún no das tus primeros pasos en los microservicios, aprovecha el descuento que te traemos✌🏻

En este artículo, mostraré un caso práctico de implementación de una nueva característica y su prueba en una arquitectura de microservicios en la nube y arrojaré algo de luz sobre algunos pilares del desarrollo de software moderno, como Feature Toggles y Canary Deployments (o Releases), que utilizan grandes empresas tecnológicas como Facebook y que también admite AWS.

Pero no te preocupes, aunque el tema puede ser peliagudo, voy a simplificarte las cosas.

Sin más preámbulos, empecemos. ✌🏻

En nuestra última reunión de planificación, añadimos una tarea a nuestro sprint backlog actual relativa a un cambio en la lógica de nuestros formularios. En los próximos meses, los usuarios de nuestra plataforma B2B GreenEnergy tendrán que rellenar un campo específico cada vez que creen un nuevo pedido. Anteriormente, el campo era opcional.

Originalmente hemos implementado la lógica de validación para los campos del formulario en una aplicación Java monolítica -que representa nuestra API REST- y la hemos alojado en una flota de instancias AWS EC2. Pero recientemente hemos añadido un nuevo servicio a nuestra familia de microservicios y lo hemos implementado como AWS Lambda.

Si tu quieres aprender acerca de Aws recuerda que puedes visitar en Udemy el curso (Tiene descuento para ti)

Debido a una versión canaria, no todos nuestros clientes están utilizando esta Lambda recién nacida, lo que significa que tenemos que mantener esta validación duplicada hasta que la eliminemos en un futuro próximo.

Si tienes curiosidad por saber qué es y qué hace un "canary release", aquí tienes una breve definición:

"Canary release es una técnica para reducir el riesgo de introducir una nueva versión de software en producción desplegando lentamente el cambio a un pequeño subconjunto de usuarios antes de desplegarlo a toda la infraestructura y ponerlo a disposición de todo el mundo." - Danilo Sato

Añadir un método de validación


Así que, para ajustar el código fuente con este nuevo requisito, empiezo con nuestro viejo gran "microservicio".

Allí, implemento un nuevo método isValidRefNumber() en una clase Validator, donde añado una expresión regular que comprueba el valor del campo:

private boolean isValidReferenceNumber(String refNumber) {
    Pattern pattern = Pattern.compile("[1-9]\\d{8}"); // 9 digits and does not start with 0
    return pattern.matcher(refNumber).matches();
}

Añadir un nuevo código de error


Observo la necesidad de actualizar un proyecto de núcleo compartido añadiéndole el nuevo código de error INVALID_REF_NUMBER asociado a mi campo:

public enum FieldsErrorCode {
    // ...
    INVALID_REF_NUMBER("REF_NUMBER_ERROR", 25001);
    
    private final String errorName;
    private final int errorCode;

    FieldsErrorCode(String errorName, int errorCode) {
        this.errorName = errorName;
        this.errorCode = errorCode;
    }
    
    public String getErrorCode() {
        return errorCode + " - " + errorName;
    }
}

Añadir un nuevo Feature Toggle


"Los Feature Toggles (a menudo también denominados Feature Flags) son una técnica poderosa, que permite a los equipos modificar el comportamiento del sistema sin cambiar el código." Pete Hodgson

Dado que hemos planificado la activación de esta validación para el futuro, necesito añadir un Feature Toggle e inicializarlo con false en el archivo *.yml:

import org.togglz.core.util.NamedFeature;

public class FeatureToggles {
    // ...
    public static final NamedFeature VALIDATE_REF_NR_FIELD = new NamedFeature("VALIDATE_REF_NR_FIELD");
    
    private FeatureToggles() {
    }
}
feature-toggles:
    VALIDATE_REF_NR_FIELD:
        enabled: false

y llamar a la lógica de validación sólo cuando el Toggle es true

import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import static com.webenius.core.FieldsErrorCode.INVALID_REF_NUMBER;

public class FieldsValuesValidator implements Validator {
 
  // ...
  
  public void validateValues(Order order, Errors errors) {

    List<Field> fields = order.getFields();
    // ...
    if (featureTogglesService.isActive(Toggles.VALIDATE_REF_NR_FIELD)) {
      validateRefNumber(errors, fields);
    }
  }

  // ...

  private void validateRefNumber(Errors errors, List<Field> fields) {

    Optional<Field> refNumber = fileds.stream().filter(a -> a.getName() != null && a.getName().equals(REF_NR)).findFirst();

    if (refNumber.isEmpty()) {
      return;
    }
    
    if (!isValidRefNumber(refNumber.get().getValue())) {
      errors.rejectValue(ERROR_FIELD_NAME_REF_NR, INVALID_REF_NUMBER.name(),
              new Object[] { refNumber.get().getName() }, "The Ref.-Nr is not correct!");
    }
  }

  private boolean isValidRefNumber(String refNumber) {
    Pattern pattern = Pattern.compile("[1-9]\\d{8}"); // 9 digits and does not start with 0
    return pattern.matcher(refNumber).matches();
  }
  
}

Actualizar el servicio de pedidos


El servicio de pedidos está desplegado (como ya he comentado) como un AWS Lambda. Así que necesito añadirle una variable de entorno, en mi archivo configuration.py:

class Configuration(Enum):
    # ...
    VALIDATE_REF_NR_FIELD = (os.getenv('VALIDATE_REF_NR_FIELD', 'false') == 'true')

Y para que esta variable se añada automáticamente a la configuración de Lambda en AWS, necesito añadirla a los archivos *.tf de Terraform:

variable "validate_ref_nr_field" {
  type = bool
  default = false
}
resource "aws_lambda_function" "order_service_lambda_function" {
    // ...
    environment {
        variables = {
            // ...
            VALIDATE_REF_NR_FIELD = var.validate_ref_nr_field
        }
    }
    // ...
}

Entonces puedo usar esta variable de entorno en mi script ValidateRefNr y dependiendo de su valor, ejecutar la lógica apropiada:

import re

class ValidateRefNr:

    # ...
    REF_NR = "Ref.-Nr"

    def validate(self, fields_to_validate):

        validate_ref_nr = Configuration.VALIDATE_REF_NR_FIELD.value

        is_field_expected = list(filter(lambda field: field["name"] == self.REF_NR))

        if is_field_expected:
            found_fields = list(filter(lambda field: field["name"] == self.REF_NR, fields_to_validate))

            ref_nr = found_fields[0]["value"] if "value" in found_fields[0].keys() \
                else found_fields[0]["values"][0]

            if validate_ref_nr and (ref_nr is None or not self.isNineDigits(ref_nr)):
                return [FieldResponse(self.ERROR_PROPERTY_NAME, FieldsErrorCode.INVALID_REF_NUMBER, "The field Ref.-Nr is not correct!")]

        return [FieldResponse.valid_field_response()]


    def isNineDigits(self, ref_nr):
        # 9 digits and does not start with 0
        matched = re.match("[1-9]\\d{8}", ref_nr)
        is_valid = bool(matched)
        return is_valid

Compilación, CI, CD


Después de implementar algunas pruebas unitarias y comprobarlas, ahora estoy lista para un commit y un push en la rama master.

Esto significa que, en cuestión de minutos, mi código estará disponible en nuestros diferentes entornos, incluido el entorno en vivo, porque hemos adoptado el enfoque de entrega continua (CD). Una vez que la construcción en nuestra herramienta de Integración Continua (CI), y termine con éxito, todo irá en vivo.

Pero, por supuesto, el valor false con el que he inicializado mi feature toggle VALIDATE_REF_NR_FIELD en el archivo *.yml impedirá que se ejecute mi validación.

feature-toggles:
    VALIDATE_REF_NR_FIELD:
        enabled: false

Si miro el servicio de pedidos Lambda en la consola de AWS, y compruebo su pestaña Configuración, también veré mi recién añadida variable de entorno inicializada con false:

Escenarios de prueba / QA


Escenario de prueba 1
Aunque he realizado algunas pruebas en mi localhost, necesito repetir el proceso en el entorno de pruebas.

He preparado algunos datos de prueba en formato JSON que enviaré directamente a la API REST con Postman como carga útil de mi solicitud de creación de pedido y, a continuación, comprobaré las respuestas manualmente. Parte de nuestros usuarios finales aún no utilizan nuestra aplicación frontend y se comunican con nuestro sistema de esta forma debido a la integración de aplicaciones de terceros con nuestra API.

Antes de hacerlo, tengo que llamar a la petición POST que me permite cambiar los valores de dos toggles de características:

VALIDATE_REF_NR_FIELD (el flag que he añadido yo mismo arriba): debe ser true.
VALIDATE_REF_NR_FIELD_IN_ORDER_SERVICE: debe ser false para poder ejecutar la lógica de validación en la API REST.

Escenario de prueba 2
Para mi segundo escenario de prueba, necesito establecer el toggle VALIDATE_REF_NR_FIELD_IN_ORDER_SERVICE como true y el config param VALIDATE_REF_NR_FIELD como true para comprobar la lógica en la Lambda de Python.

Escenario de prueba 3


En mi último escenario de prueba, tengo que llamar a la aplicación de frontend y verificar que en el formulario de pedido el REF_NR tiene * junto a su etiqueta (desde que se convirtió en obligatorio) y que el usuario recibe los mensajes/códigos de error apropiados cuando no da ningún valor o da un valor no válido para ese campo.

Conclusión
No cabe duda de que las arquitecturas de microservicios en la nube suscitan un enorme interés y uso. Han dado lugar a muchos enfoques y paradigmas para resolver problemas y alcanzar diversos objetivos. La elección de una solución adecuada depende básicamente de los requisitos de su proyecto, el tamaño/naturaleza de su público y lo que ofrezca su proveedor o proveedores de nube.

Patrones y prácticas como las versiones "canary" y las "feature flags" podrían ayudarle a conseguir una entrega continua y evitar que la experiencia del usuario se vea entorpecida. Sin embargo, puede que no se adapten bien a su caso. Pueden generar código desordenado, lo que introduce una capa de complejidad adicional a su proyecto.

Fuente