Zurab Bely, líder de equipo, práctica de Java, cuenta su historia de trabajar en un proyecto para una gran empresa y comparte su experiencia.
Cómo me instalé ...
Ingresé al proyecto a fines del verano de 2017 como desarrollador ordinario. No puedo decir que en ese momento me gustaba mucho: las tecnologías utilizadas en el proyecto eran antiguas, la comunicación dentro del equipo se minimizaba, la comunicación con el cliente era difícil e improductiva. Entonces el proyecto me conoció. En ese momento, solo tenía un deseo: salir rápidamente de él.
Te contaré un poco sobre el proyecto en su conjunto. Este es el portal oficial de una gran empresa con información general, noticias, promociones y otro contenido. Todos los boletines de marketing contienen enlaces a ciertas páginas del sitio, es decir, la carga es estable, pero en ciertos momentos puede alcanzar valores altos. La estabilidad y accesibilidad de la aplicación web requiere atención especial: cada minuto de inactividad del servicio genera grandes pérdidas para el cliente.
Chabola que entrecerró los ojos en el viento
Al principio, estudié principalmente la condición técnica del proyecto, solucioné errores menores e hice mejoras menores. Desde un punto de vista técnico, la aplicación se veía horrible: una arquitectura monolítica construida alrededor de la versión comercial obsoleta de dotCMS, código escrito en la sexta versión de Java, cuando la novena representación del lado del servidor de la parte del cliente en el marco Velocity, que no tenía varios años para ese momento, ya se había lanzado fue apoyado. Cada instancia se lanzó en JBoss AS y se enrutó usando Nginx. Las pérdidas de memoria llevaron a un reinicio constante de los nodos, y la falta de almacenamiento en caché normal condujo a un aumento en la carga del servidor. Pero la mayor astilla fueron los cambios realizados en el código CMS. Descartaron la posibilidad de una actualización indolora a una versión más reciente. Una buena ilustración de esto fue la transición de la versión 3.2 a la 3.7, que acababa de terminar en ese momento. La transición a la próxima versión menor tomó más de un año. Ninguna solución popular, como Spring Framework, React.js, arquitectura de microservicios, Docker, etc., estaba fuera de discusión. Al profundizar en el proyecto, se hicieron visibles las consecuencias de tal condición técnica. La más aguda de ellas fue la incapacidad de ejecutar la aplicación localmente para el desarrollo y la depuración. Todo el equipo de 8 personas trabajó en un puesto de desarrollo, donde se implementó una copia de la versión de producción de la aplicación. En consecuencia, solo un desarrollador podía depurar su código al mismo tiempo, y rodar el código actualizado bloqueó a todo el equipo. El apogeo fue una venta fallida, durante la cual se enviaron millones de cartas, SMS y notificaciones push a diferentes usuarios a través de decenas de canales: se abrieron decenas de miles de sesiones al mismo tiempo. Los servidores no podían soportarlo, y la mayoría de las veces el portal no estaba disponible. La aplicación no escala bien. Solo había una forma de hacer esto: desplegar otra copia una al lado de la otra y equilibrar las cargas entre ellas usando Nginx. Y cada entrega del código de producción implicaba mucho trabajo manual y tomó varias horas.
Seis meses después de mi participación en el proyecto, cuando la situación ya comenzaba a descontrolarse, se tomó la decisión de cambiar radicalmente la situación. El proceso de transición ha comenzado. Los cambios afectaron todas las áreas: composición del equipo, procesos de trabajo, arquitectura y componente técnico de la aplicación.
Construimos, construimos ...
En primer lugar, se han producido cambios de personal. Reemplazados por varios desarrolladores, me convirtieron en un líder de equipo. La transición a soluciones modernas comenzó con la participación de personas en el equipo que tenían experiencia trabajando con ellos.
Los cambios de procedimiento fueron más globales. Para entonces, el desarrollo ya estaba en marcha en la metodología Agile- + Scrum, sprints de dos semanas con entrega al final de cada iteración. Pero, de hecho, esto no solo no aumentó la velocidad del trabajo, sino que, por el contrario, se ralentizó. Las manifestaciones diarias se prolongaron durante una hora y media o dos horas y no produjeron ningún resultado. La planificación y la preparación se convirtieron en disputas, palabrotas o comunicación simple. Había algo que ver con esto. Inicialmente fue muy difícil cambiar algo en este sentido: en nombre del cliente, el equipo casi perdió la confianza, especialmente después de una venta fallida. Cada cambio tuvo que ser justificado, discutido y probado durante mucho tiempo. Curiosamente, pero la iniciativa vino del cliente. Por su parte, un scrum-master estuvo involucrado para controlar la correcta aplicación de enfoques y metodologías, depurar procesos y poner al equipo a trabajar. Aunque se sintió atraído por unos pocos sprints, nos ayudó a ensamblar adecuadamente los cimientos. El enfoque de la comunicación con el cliente ha cambiado mucho. Comenzamos a discutir los problemas en los procesos con mayor frecuencia, las retrospectivas comenzaron a tener lugar de manera más productiva, los desarrolladores estaban más dispuestos a dar su opinión, y el cliente, por su parte, avanzó y apoyó el proceso de transición en todos los sentidos.
Pero, sinceramente, diré sinceramente: hubo algunos momentos en que algunos cambios dentro del equipo se llevaron a cabo "a ciegas", y después de la aparición de resultados positivos, esto se informó al cliente. Durante seis meses, la actitud ha cambiado a una comunicación de trabajo cómoda. Esto fue seguido por varias reuniones de trabajo en equipo, reuniones de un día y dos días de todo el equipo de desarrollo con el equipo del cliente (comercializador, analista, diseñador, producto, gerentes de contenido, etc.), visitas conjuntas al bar. Después de un año, y hasta el día de hoy, la comunicación se puede llamar amigable. El ambiente se ha vuelto amigable, relajado y confortable. Por supuesto, no pasa sin conflictos, pero incluso en la familia más feliz a veces hay disputas.
No menos cambios interesantes ocurrieron durante este período en el código de la aplicación, en su arquitectura y soluciones utilizadas. Si no tiene conocimientos técnicos, puede omitir con seguridad el texto completo hasta la conclusión. Y si tienes suerte como yo, ¡bienvenido! Toda la transición se puede dividir en varias etapas. Sobre cada uno con más detalle.
Etapa 1. Identificación de áreas problemáticas críticas.Todo fue lo más simple y claro posible. En primer lugar, era necesario deshacerse de la dependencia de un producto comercial de terceros, cortar un monolito y hacer posible la depuración local. Quería separar el código de cliente y servidor, para distribuirlo arquitectónicamente y físicamente. Otro lugar problemático es la calificación. El proyecto carecía por completo de pruebas automáticas. Esto hizo la transición un poco difícil, ya que todo tenía que verificarse manualmente. Dado que nunca ha habido tareas técnicas para lo funcional (los detalles del proyecto se ven afectados aquí), hubo una alta probabilidad de perder algo. Habiendo pintado las áreas problemáticas, una vez más miramos la lista. Parecía un plan. ¡Es hora de construir un rascacielos!
Etapa 2. Actualización de la base del código.La etapa de mayor duración. Todo comenzó con la transición a una arquitectura de servicio (que no debe confundirse con microservicios). La idea era la siguiente: dividir la aplicación en varios servicios separados, cada uno de los cuales se ocupará de su tarea específica. Se suponía que los servicios no eran "micro", pero tampoco quería poner todo en una sola caldera. Se suponía que cada servicio era una aplicación Spring Boot escrita en Java SE 8 y se ejecutaba en Tomcat.
El primero fue el llamado. "Servicio de contenido", que se ha convertido en una capa entre la aplicación futura y el CMS. Se ha convertido en una abstracción en el camino hacia el contenido. Se supuso que todas las solicitudes que realizamos previamente directamente en el CMS se realizarán a través de este servicio, pero que ya utilizan el protocolo HTTP. Dicha solución nos permitió reducir la conectividad y creó la posibilidad de reemplazar posteriormente dotCMS con un análogo más moderno o incluso eliminar el uso de productos comerciales y escribir nuestra propia solución adaptada a nuestras tareas (mirando hacia el futuro, diré que este es el camino que tomamos).
Inmediatamente creó el terreno para la separación del código frontal y de fondo. Crearon un servicio front-end, que se hizo responsable de distribuir el código escrito en la reacción. Atornillamos npm, configuramos el nodo y depuramos el ensamblaje; todo es como debería de acuerdo con las tendencias modernas de la parte del cliente.
En general, la funcionalidad se asignó al servicio de acuerdo con el siguiente algoritmo:
- creó una nueva aplicación Spring Boot con todas las dependencias y configuraciones necesarias;
- portó toda la lógica básica (a menudo la escribió desde cero, refiriéndose al código anterior, solo para asegurarse de que no se olvidara de ningún matiz), por ejemplo, para el servicio de almacenamiento en caché, estas son las opciones para agregar al caché, leerlo y desactivarlo;
- todas las nuevas funcionalidades siempre se han escrito utilizando el nuevo servicio;
- reescribiendo gradualmente piezas antiguas de la aplicación en un nuevo servicio en orden de importancia.
Al principio, teníamos algunos de ellos:
- Servicio de contenidos. Actuó como una capa entre la aplicación y el CMS.
- Servicio de caché. Repositorio simple en Spring Cache.
- Servicio de AA. Al principio, solo se dedicaba a la distribución de información sobre un usuario autorizado. El resto permaneció dentro de dotCMS.
- Servicio al frente. Responsable de distribuir el código del cliente.
Etapa 3. Autotests.Teniendo en cuenta toda la experiencia del proyecto, decidimos que la presencia de pruebas funcionales simplifica enormemente la vida y la posible actualización adicional de la aplicación. Es hora de introducirlos en el proyecto. Las pruebas unitarias del código, lamentablemente decir esto, se estancaron casi de inmediato. Tomaron mucho tiempo para escribir y dar soporte, y teníamos muy poco, porque, además de reescribir el código, las tareas actuales sobre la nueva funcionalidad nos colgaban y a menudo aparecían errores. Se decidió centrarse solo en probar la interfaz de la aplicación utilizando Selenium. Esto, por un lado, nos facilitó realizar pruebas de regresión antes de entregarlo a producción, por otro lado, fue posible refactorizar en el lado del servidor, monitorear el estado en el lado del cliente. El equipo no tenía un automatizador, y escribir y mantener la relevancia de las pruebas automáticas requiere costos adicionales. No volvieron a capacitar a uno de los evaluadores, y se agregó otra persona al equipo.
Etapa 4. Automatización de implementación.Ahora que tenemos servicios separados, cuando el frontend se separó del backend, cuando la funcionalidad principal comenzó a ser cubierta por las pruebas automáticas, es hora de abrir una lata de cerveza y automatizar todo el trabajo manual de implementación y soporte de la aplicación localmente, en servidores de demostración y producción. Cortar el monolito en pedazos y el uso de Spring Boot nos ha abierto nuevos horizontes.
Los desarrolladores pudieron depurar el código localmente, ejecutando solo esa parte de la funcionalidad que se necesita para esto. Los bancos de pruebas finalmente comenzaron a usarse para su propósito previsto: ya había un código más o menos depurado, listo para las pruebas iniciales y de calificación. Pero todavía había mucho trabajo hecho a mano que desperdiciaba nuestro valioso tiempo y energía. Después de estudiar el problema y clasificar las soluciones, nos decidimos por un grupo de Gradle + TeamCity. Dado que el proyecto fue construido por Gradle, agregar algo nuevo no tenía sentido, y los scripts escritos resultaron ser independientes de la plataforma, se pueden ejecutar en cualquier sistema operativo, de forma remota o local. Y esto permitió no solo usar cualquier solución para CI / CD, sino también cambiar sin problemas la plataforma a cualquier otra. TeamCity fue elegido debido a su rica funcionalidad incorporada, la presencia de una gran comunidad y una larga lista de complementos para todas las ocasiones, así como la integración con el entorno de desarrollo Intellij IDEA.
Por el momento, hay más de 100 scripts de optimización y más de 300 tareas en el sistema CI para ejecutarlas con diferentes parámetros. Esto no es solo un despliegue para probar bancos y entregarlo a producción, sino que también funciona con registros, administración de servidores, enrutamiento y solo soluciones para tareas rutinarias del mismo tipo. Algunas de las tareas se han eliminado de nuestros hombros. Los administradores de contenido pudieron vaciar el caché ellos mismos. Los chicos del soporte técnico tuvieron la oportunidad de obtener servicios individuales por su cuenta, para llevar a cabo acciones de reanimación primaria. El sueño de los desarrolladores se ha vuelto más profundo y tranquilo.
Etapa 5. CMS propio.Después de que fue posible hacer un resumen del CMS comercial, se hizo posible recibir datos de otra fuente, sin tener que volver a escribir el código de la aplicación. El servicio de contenido decidió dónde obtener esta o aquella información. Después de buscar soluciones preparadas y analizarlas, decidimos escribir nuestro propio sistema de administración de contenido, ya que ninguno de ellos satisfizo plenamente nuestras necesidades. Escribir su propio CMS es un proceso interminable, constantemente aparecen nuevas necesidades y deseos. Seleccionamos algunas características básicas y fuimos al hermoso mundo del desarrollo. Para lanzar la primera versión en prod, tuvimos un mes y medio de hombre. Como la funcionalidad está lista en el nuevo CMS, transferimos contenido del anterior al mismo. Todas las páginas nuevas ya no tienen nada que ver con dotCMS. Con el tiempo, esto hizo posible abandonar la versión paga y cambiar a la versión comunitaria, y en el futuro abandonar completamente algo de terceros.
Etapa 6. Corrección de pruebas.Después de enrollarnos los pantalones, comenzamos nuestro viaje al mundo de la programación hipster. Esta etapa para mi proyecto fue la final en el proceso de reestructuración. Continúa hasta el día de hoy. El área principal para la que generalmente apareció esta etapa es la escala. El paquete Docker + Kubernetes + Consul le permite no solo facilitar la implementación en diferentes servidores y administrar cada servicio por separado, sino también escalar de manera flexible toda la aplicación, hacerlo solo en aquellos lugares donde sea necesario y solo durante el tiempo que sea necesario. Con más detalle, puedo pintar solo cuando cambiamos completamente a esta solución en producción.
... y finalmente construido. ¡Hurra!
Ha pasado un año desde que comenzó la actualización de la aplicación. Estos son ahora 26 servicios REST escritos en Spring Boot. Cada uno tiene documentación detallada de la API en la interfaz de usuario de Swagger. El lado del cliente está escrito en React.js y separado del lado del servidor. Todas las páginas principales del portal están cubiertas con pruebas. Realizó varias ventas importantes. La transición a las tecnologías modernas, la eliminación del código antiguo y el funcionamiento estable de los servidores motivan fuertemente a los desarrolladores. Desde "como dijimos, lo hacemos", el proyecto se trasladó a la corriente principal, donde todos están interesados en el éxito, ofrece sus propias opciones para mejoras y optimización. La actitud del cliente hacia el equipo ha cambiado, se ha creado una atmósfera amigable.
Este es el resultado del trabajo de todo el equipo, cada desarrollador y probador, gerentes y clientes. Fue muy duro, nervioso y a veces al borde de una falta. Pero la cohesión del equipo, la planificación competente y el conocimiento de los resultados hicieron posible superar todas las dificultades.