Los 10 Errores Más Comunes De Spring Framework

0
155
Noticias de tecnología para Guatemala y latinoamérica
Compartir

Spring es sin duda uno de los frameworks más populares de Java, y también una bestia difícil de domar. Mientras que sus conceptos básicos son muy fáciles de entender, convertirse en un desarrollador fuerte de Spring requiere algo de tiempo y esfuerzo.

En este artículo vamos a cubrir algunos de los errores más comunes en Spring, específicamente orientado hacia aplicaciones web y Spring Boot. Como lo vemos en la página web de Spring Boot, éste toma un punto de vista sobre cómo las aplicaciones de producción ya lista se deberían construir, así que este artículo tratará de imitar esa visión y proporcionar un resumen de algunos consejos que se podrían incorporar en el desarrollo de una aplicación web Sping Boot estándar.

En caso de que no conozcas Spring Boot pero aun así te gustaría probar algunas de las cosas que se mencionan, creé un repositorio GitHub que acompaña este artículo. Si te sientes perdido en algún momento durante este artículo, te recomendaría clonar el repositorio y jugar con el código en tu máquina local.

Error Común #1: Ir A Un Nivel Muy Bajo

Comenzamos con este error común porque el síndrome “no inventado aquí” es muy común en el mundo del desarrollo software. Los síntomas incluyen, re-escribir, regularmente, piezas de código usado comúnmente y muchos desarrolladores parecen sufrir por esto.

Mientras que entender el trabajo interno de una biblioteca y su implementación es en gran parte bueno y necesario (y, también, puede ser un gran proceso de aprendizaje), es perjudicial para tu desarrollo como ingeniero software estar, constantemente lidiando con los mismos detalles de implementación de bajo nivel. Hay un motivo por el cual existen las abstracciones y frameworks como Spring, el cual es precisamente separarte de manuales de trabajo repetitivos y permitir que te concentres en detalles de más alto nivel—tus objetos de dominio y lógica de negocios.

Así que acepta las abstracciones – la próxima vez que te enfrentes con un problema en particular, haz una búsqueda rápida primero y determina si alguna biblioteca que resuelva ese problema ya está integrada a Spring; en estos tiempos, es probable que encuentres una solución adecuada ya existente. Como ejemplo de una biblioteca útil, usaré como ejemplos las anotaciones de Proyecto Lombok en lo que resta de este artículo. Lombok se usa como un generador de código modelo y así, el desarrollador perezoso dentro de ti no debería tener problema alguno al familiarizarse con la biblioteca. Como ejemplo, observa como se ve un “Java Bean estándar” con Lombok:

@Getter
@Setter
@NoArgsConstructor
public class Bean implements Serializable {
   int firstBeanProperty;
   String secondBeanProperty;
}

Como te puedes imaginar, el código de arriba compila:

public class Bean implements Serializable {
   private int firstBeanProperty;
   private String secondBeanProperty;
 
   public int getFirstBeanProperty() {
       return this.firstBeanProperty;
   }
 
   public String getSecondBeanProperty() {
       return this.secondBeanProperty;
   }
 
   public void setFirstBeanProperty(int firstBeanProperty) {
       this.firstBeanProperty = firstBeanProperty;
   }
 
   public void setSecondBeanProperty(String secondBeanProperty) {
       this.secondBeanProperty = secondBeanProperty;
   }
 
   public Bean() {
   }
}

Sin embargo, ten en cuenta que es muy probable que tengas que instalar un plugin en caso de que quieras usar Lombok con tu IDE. La versión IntelliJ IDEA del plugin se puede encontrar aquí.

Error Común #2: ‘Filtrar’ Trabajo Interno

Exponer tu estructura interna nunca es buena idea, ya que crea una inflexibilidad en el diseño de servicio y consecuentemente promueve malas prácticas de código. ‘Filtrar’ trabajo interno se manifiesta al hacer accesible la estructura de base de datos desde ciertos puntos de salida API. Como ejemplo, digamos que el siguiente POJO (“Plain Old Java Object”) representa una tabla en tu base de datos:

@Entity
@NoArgsConstructor
@Getter
public class TopTalentEntity {
 
   @Id
   @GeneratedValue
   private Integer id;
 
   @Column
   private String name;
 
   public TopTalentEntity(String name) {
       this.name = name;
   }
 
}

Digamos que existe un punto de salida que necesita acceder a la data TopTalentEntity. Aunque es muy tentador regresar instancias de TopTalentEntity, una solución más flexible puede ser crear una nueva clase para representar la data TopTalentEntity en el punto de salida de la API:

@AllArgsConstructor
@NoArgsConstructor
@Getter
public class TopTalentData {
   private String name;
}

Así, hacer cambios al back-end de tu base de datos no requerirá ningún cambio adicional en la capa de servicios. Considera lo que sucedería en el caso de que se agregara un campo de ‘contraseña’ a TopTalentEntity para guardar la función hash de la contraseña de los usuarios en la base de datos – sin algún conector como TopTalentData, olvidar cambiar el servicio front-end expondría accidentalmente, ¡información secreta no deseable!

Error Común #3: Falta de Separación de Preocupaciones

Mientras tu aplicación crece, la organización de código se convierte rápidamente en algo mucho más importante. Irónicamente, la gran parte de los principios de una buena ingeniería software comienza a romperse en escala – en especial, en casos donde no se ha pensado bien en el diseño de la arquitectura de la aplicación. Uno de los errores más comunes al que los desarrolladores tienden a sucumbir, es maximizar las preocupaciones de código y es ¡extremadamente fácil de hacer!

Lo que usualmente rompe la separación de preocupaciones es solo ‘lanzar’ una nueva funcionalidad a clases ya existentes. Esto es, una excelente solución a corto plazo (para empezar, requiere menos mecanografía) pero inevitablemente se convierte en un problema más adelante, ya sea durante el período de pruebas, mantenimiento o en algún momento entre estos dos. Considera el siguiente controlador, el cual regresa TopTalentData desde su repositorio:

@RestController
public class TopTalentController {
 
   private final TopTalentRepository topTalentRepository;
 
   @RequestMapping("/toptal/get")
   public List<TopTalentData> getTopTalent() {
       return topTalentRepository.findAll()
               .stream()
               .map(this::entityToData)
               .collect(Collectors.toList());
   }
 
   private TopTalentData entityToData(TopTalentEntity topTalentEntity) {
       return new TopTalentData(topTalentEntity.getName());
   }
 
}

En primera instancia, puede parecer que no hay algo particularmente incorrecto con este código; proporciona una lista de TopTalentData la cual está siendo extraída desde las instancias de TopTalentEntity. Al mirar más de cerca, sin embargo, podemos ver que hay algunas cosas que están siendo llevadas a cabo por TopTalentController; principalmente está mapeando peticiones a un punto de salida en particular, extrayendo data de un repositorio y convirtiendo entidades recibidas desde TopTalentRepository a un formato diferente. Una solución más ‘limpia’ sería separar esas preocupaciones en sus propias clases. Podría verse algo como esto:

@RestController
@RequestMapping("/toptal")
@AllArgsConstructor
public class TopTalentController {
 
   private final TopTalentService topTalentService;
 
   @RequestMapping("/get")
   public List<TopTalentData> getTopTalent() {
       return topTalentService.getTopTalent();
   }
}
 
@AllArgsConstructor
@Service
public class TopTalentService {
 
   private final TopTalentRepository topTalentRepository;
   private final TopTalentEntityConverter topTalentEntityConverter;
 
   public List<TopTalentData> getTopTalent() {
       return topTalentRepository.findAll()
               .stream()
               .map(topTalentEntityConverter::toResponse)
               .collect(Collectors.toList());
   }
}
 
@Component
public class TopTalentEntityConverter {
   public TopTalentData toResponse(TopTalentEntity topTalentEntity) {
       return new TopTalentData(topTalentEntity.getName());
   }
}

Una ventaja adicional de esta jerarquía es que nos permite determinar dónde reside la funcionalidad, solo con inspeccionar el nombre de la clase. Además, durante el período de prueba podemos sustituir fácilmente cualquiera de las clases con un simulacro de una implementación si es necesario.

Error Común #4: Manejo Pobre e Inconsistente de Errores

El tema de la inconsistencia no es necesariamente exclusivo de Spring (o Java) pero sigue siendo una faceta importante a considerar cuando se trabaje en proyectos de Spring. Mientras que el estilo de codificación se puede debatir (y es usualmente un tema de acuerdo dentro de un equipo o una compañía entera), tener un estándar común puede ser una gran ayuda de productividad. Esto es muy verdadero, en especial con equipos multi personales; la consistencia permite que el rechazo ocurra sin que se gasten muchos recursos en reconfortar o proporcionar largas explicaciones en cuanto a las responsabilidades de las diferentes clases.

Considera un proyecto Spring con sus diferentes archivos de configuración, servicios y controladores. Al ser semánticamente consistente al darles nombre crea una estructura fácil de investigar, donde todo desarrollador por muy nuevo que sea, puede manejarse alrededor del código; adjuntar sufijos de configuración a las clases de configuración, sufijos de Servicio a tus servicios y sufijos de Controlador a tus controladores, por ejemplo.

Cercanamente relacionado al tema de consistencia, se encuentra el manejo de errores del lado del servidor, el cual merece un énfasis específico. Si en algún momento has tenido que manejar respuestas de excepción de una API mal escrita, probablemente sabes porque – puede ser engorroso analizar excepciones de manera apropiada, y aún más engorroso es determinar la razón por la que esas excepciones ocurrieron inicialmente.

Como desarrollador API, idealmente deberías querer cubrir todos los puntos de salida que enfrentan los usuarios y traducirlos a un formato de error común. Esto, usualmente, significa tener un código de error genérico y una descripción, en vez de la solución pretexto como a) regresar el mensaje “500 Error de Servidor Interno”, o b) dejar que el usuario haga la búsqueda de la solución (lo cual se debe evitar fervientemente, ya que expone tu trabajo interno, aparte de ser esto difícil de manejar por el cliente).

Un ejemplo de un error común de formato de respuesta puede ser:

@Value
public class ErrorResponse {
 
   private Integer errorCode;
   private String errorMessage;
 
}

Algo similar a esto se encuentra comúnmente en las API más populares, y tiende a funcionar muy bien ya que puede ser documentada fácil y sistemáticamente. Se pueden traducir excepciones a este formato al proporcionar la anotación @ExceptionHandler a un método (un ejemplo de una anotación está en el Error Común #6).

Error Común #5: Lidiar de Manera Incorrecta con el Multihilo

Sin importar si se encuentra en escritorios o aplicaciones web, en Spring o no, el multihilo puede ser difícil de manejar. Los problemas causados por ejecuciones paralelas de programas están cargados de tensión, son elusivos y extremadamente difíciles de depurar – de hecho, dada la naturaleza del problema, una vez que te das cuenta de que estás tratando con un problema de ejecución paralela, probablemente tengas que proceder con el depurador enteramente, e inspeccionar tu código “a mano” hasta que encuentres la causa del error de raíz. Desafortunadamente, una solución regular no existe para resolver tales problemas; dependiendo de tu caso específico, tendrás que estudiar la situación y luego atacar el problema desde el ángulo que creas mejor.

Por supuesto, idealmente querrías evitar multihilar los bugs completamente. De nuevo, un acercamiento estándar no existe, pero aquí hay unas consideraciones prácticas para depurar y prevenir errores de multihilo:

Evita el Estado Global

Primero, siempre recuerda el problema del “estado global”. Si estás creando una aplicación de multihilo, absolutamente todo que sea modificable debería monitorearse muy de cerca y, si es posible, eliminarlo completamente. Si hay algún motivo por el cual la variable global deba mantenerse modificable, usa muy cuidadosamente la sincronización y rastrea el desempeño de tu aplicación para confirmar que no está fallando debido a los nuevos períodos de espera.

Evita la Mutabilidad

Ésta viene desde la programación funcional y, adaptada a OOP, dice que la clase de mutabilidad y cambiar el estado debería evitarse. Esto, en resumen, significa proceder con métodos de fijación y tener campos finales privados en todas tus clases modelo. El único momento en que sus valores se pueden mutar es durante la construcción. De esta manera te puedes asegurar de que no surjan problemas de contención y que las propiedades de objeto que acceden, siempre proveerán los valores correctos.

Registra Data Crucial

Evalúa dónde podría causar problemas tu aplicación y de manera preventiva, registra toda la data crucial. Si ocurre un error, estarás agradecido de tener información que diga cuales peticiones se recibieron y tendrás mejor percepción de porque tu aplicación falló. Es necesario notar, nuevamente, que el registro introduce el archivo adicional I/O y, por ende, no se debería abusar de éste, ya que podría impactar severamente el desempeño de tu aplicación.

Re-Usa Implementaciones Existentes

Cuando necesites generar tus propios hilos (ej. Para hacer peticiones asincrónicas a diferentes servicios), reutiliza implementaciones seguras ya existentes, en vez de crear tus propias soluciones. Esto, mayormente, significa que deberás utilizar ExecutorServices y CompletableFutures de Java 8 con su estilo limpio y funcional para la creación de hilo. Spring también permite procesar peticiones asincrónicas a través de la clase DeferredResult.

via: toptal.com

Dejar respuesta

Please enter your comment!
Please enter your name here