Transformación de procesos de desarrollo y entrega para una aplicación heredada

Nuestro equipo es responsable de la operación y desarrollo de un gran producto corporativo.
A principios de 2017, tomando un descanso de una implementación importante y releyendo las "lecciones aprendidas", decidimos firmemente revisar el desarrollo y la entrega de nuestra aplicación. Nos preocupaba la baja velocidad y la calidad de la entrega, lo que no nos permitía proporcionar el nivel de servicio que los clientes esperan de nosotros.


Era hora de pasar de las palabras a los hechos, a cambiar los procesos.


Este artículo hablará brevemente sobre dónde comenzamos, qué hicimos, cuál es la situación ahora, qué dificultades hemos encontrado, qué tenemos que dejar atrás, qué más planeamos hacer.


Inicio


Un poco sobre el sistema.


La aplicación es un ejemplo clásico de una aplicación empresarial monolítica del "derrame arquitectónico de la década de 2000":


  • Operado y desarrollado durante 15 años.
  • Es un conjunto de una docena y media de WinForms, servicios de Windows y aplicaciones ASP .Net vinculadas a una sola base de datos MS SQL.
  • Tamaño de la base de código: ~ 1MLOC para C #, ~ 9000 objetos de base de datos. Gran parte de la lógica empresarial se ejecuta en el lado de la base de datos.
  • La aplicación consta de ~ 250 + soluciones para crear un cliente win / web (una solución por grupo de formularios relacionados). Este es un legado del proceso de desarrollo anterior y la arquitectura del cliente.
  • La aplicación admite varios tipos de procesos (clientes) al cambiar la configuración interna: establecer procesos, permisos, campos flexibles, etc., en las tablas de configuración de la base de datos del sistema. Al mismo tiempo, la base del código de la aplicación es la misma para todos los clientes.
  • La aplicación se implementa y admite en más de 25 sitios (cada sitio es una instancia independiente del sistema) y sirve a un total de varios miles de usuarios finales en diferentes zonas horarias.

Proceso de entrega antes de la transformación.


  1. El desarrollo y montaje de la aplicación terminada y sus componentes es realizada por el contratista.
  2. el código se almacenó en el lado del contratista (versión local de MS TFS). El código se transmite mensualmente al cliente en forma de archivo de la versión actual de la rama del repositorio principal.
  3. la entrega se llevó a cabo mediante la entrega de "actualizaciones delta": para la aplicación (conjunto de dll, exe, etc.) y componentes de la base de datos (conjunto de scripts sql create / alter). La aplicación fue construida y los paquetes delta preparados por el contratista.
  4. El proceso de despliegue fue apoyado por el sistema de transporte, los cambios se aplicaron automáticamente.

La entrega se lleva a cabo como parte de los lanzamientos mensuales (como lo he arreglado, te lo dije anteriormente aquí ).


Problemas existentes


Falta de control


  • A pesar de la propiedad formal del código, el montaje real de la aplicación por parte del cliente fue imposible.
  • Como resultado, es imposible verificar la operatividad del código transmitido al cliente.
  • cambios en el código, no transparentes para el cliente. No es posible hacer coincidir los cambios solicitados y reales en el producto.
  • El análisis de código es difícil para SQL e imposible para los componentes de C #

Aporte laboral y errores


  • La preparación de "paquetes delta" es un procedimiento que lleva mucho tiempo para el desarrollo, una fuente de errores y ciertos costos del proyecto.
  • La implementación de una aplicación Delta Packet requiere el seguimiento del orden de los paquetes. Un error de paquete fuera de servicio es un importante problema de implementación y una fuente importante de incidentes.
  • Las regresiones ocurren regularmente: los errores que parecen haber sido reparados y las correcciones implementadas en el producto aparecieron nuevamente.

Limitaciones


  • La capacidad de restaurar el estado del sistema en un momento anterior (cambios de retroceso) está prácticamente ausente.
  • La capacidad de escalar eficazmente los recursos de desarrollo y prueba temprana mediante la atracción de empleados clientes está prácticamente ausente.

Resultados esperados


Al comienzo del proyecto, establecimos objetivos obvios para resolver los problemas identificados anteriormente.


  • Transfiera el repositorio de código al control del cliente
  • Mueva el proceso de compilación de la aplicación al lado del cliente
  • Modificar el proceso de distribución de cambios, abandonando el "delta de cambios" en favor de una actualización completa

Además, utilizando las soluciones obtenidas cuando se lograron los dos primeros objetivos, calculamos:


  • Mejore la calidad técnica de las soluciones resultantes a través del control de código
  • Aumente el compromiso de prueba y la usabilidad al proporcionar implementación de autoservicio.

Etapas de un largo camino


Análisis del estado actual de los procesos de desarrollo.


Primer paso: analizar el proceso de desarrollo del contratista existente. Esto ayudó a planificar los cambios para que, si es posible, no interrumpa el trabajo.


Desafortunadamente, el conocimiento del proceso de desarrollo mostró que, en la comprensión de la industria de TI en la actualidad, el proceso estaba ausente.


  1. El código de la base de datos y la lógica comercial no se mantuvieron actualizados en el repositorio. La razón principal: la falta de herramientas que implementan el ensamblaje desde el código en el repositorio y la implementación del resultado. Entonces, el código en el repositorio es solo documentación.
  2. La versión "real" del código de la base de datos se encuentra en la "base de datos de desarrollo" común, en la que trabajan docenas de desarrolladores.
  3. El código de la aplicación del cliente (C #, ASP.NET) se mantuvo en el repositorio, pero no se garantizó la calidad y la puntualidad de las confirmaciones.
  4. Los componentes (no toda la aplicación) se ensamblaron en las estaciones del desarrollador. No está del todo claro cómo se actualizó el código antes del ensamblaje. El componente ensamblado se presentó en una carpeta compartida compartida. A partir de ahí, se formó un "paquete delta" para el cliente.
  5. La completa falta de práctica de mantener ramas de desarrollo. Por signos indirectos, sospechamos esto durante mucho tiempo, pero después de sumergirnos en el proceso, todo se hizo evidente.

Cambiar a un nuevo repositorio y sistema de control de versiones


La dependencia de las plataformas de MS y los estándares corporativos determinaron la elección del entorno de desarrollo: Team Foundation Server.
Sin embargo, cuando comenzamos el proyecto directamente (abril de 2017), la versión de Visual Studio Team Services acaba de ser lanzada. El producto parecía muy interesante, fue designado como una dirección estratégica para MS, ofreció repositorios git, ensamblaje e implementación para on-prem y cloud.


El TFS corporativo en la empresa se quedó atrás de la versión y la funcionalidad de VSTS, la migración a la nueva versión solo estaba en proceso de discusión. No quisimos esperar. Decidimos cambiar inmediatamente a VSTS, ya que esto redujo nuestros costos generales para soportar la plataforma y nos proporcionó un control total sobre cómo y qué hacemos.


En el momento del comienzo de los cambios, el equipo de desarrollo tenía experiencia con TFSVC, el código de la aplicación se almacenaba en dicho repositorio. Por otro lado, GIT se ha convertido desde hace mucho tiempo en el estándar para la comunidad de TI: el cliente y los consultores externos recomendaron cambiar a este sistema.
Queríamos que el equipo de desarrollo participara en la decisión de un nuevo sistema de control de versiones y que tomara una decisión informada.


Implementamos dos proyectos en VSTS con diferentes repositorios: TFSVC y GIT. Se definió un conjunto de escenarios, que se propuso para probar y evaluar la usabilidad en cada uno de los sistemas.


Entre los escenarios evaluados estaban:


  • Crear y fusionar sucursales
  • Organización del trabajo conjunto (en una o diferentes ramas)
  • Cambiar operaciones de cadena (commit, deshacer)
  • Integración de terceros
  • La capacidad de continuar trabajando cuando el servidor no está disponible.

Como resultado, como se esperaba, se eligió GIT, y hasta ahora nadie se ha arrepentido.


Como proceso, comenzamos a usar GitFlow. Este proceso proporcionó suficiente control sobre los cambios y permitió la entrega de versiones, como estamos acostumbrados.


  1. Defendimos la rama de desarrollo con una política que exigía que todos los cambios pasaran por solicitudes de extracción.
  2. Intentamos adherirnos a la práctica de "un ticket: una solicitud de extracción". Los cambios de diferentes boletos nunca se combinan en un solo cambio. Intentamos hacer nuestro mejor esfuerzo para probar la rama de características para evitar la situación con correcciones en los posteriores requakes de extracción.
  3. Cuando se fusiona con el desarrollo, todos los cambios se fusionan en una sola confirmación (squash).
  4. Las ramas de lanzamiento se crean a partir del desarrollo.
  5. Si es necesario, en la rama de lanzamiento, puede agregar los últimos cambios de forma selectiva (selección de cereza) o todos (rebase). No realizamos la corrección directamente en la rama de lanzamiento.
  6. Después de implementar la última versión en el producto, pasa al master a través de la fuerza de empuje (solo unas pocas personas tienen este derecho)

Automatización del montaje del producto.


La aplicación fue una gran cantidad de ensamblajes, cientos de soluciones. Como resultó durante la auditoría del proceso, todo esto se recopiló por separado y "manualmente".
En la primera etapa, decidimos no rehacer todo desde cero (para no detener la entrega existente), sino "envolver" el ensamblado en un conjunto de scripts de msbuild: un script por componente.
Por lo tanto, rápidamente obtuvimos scripts que llevaron a cabo todos los artefactos intermedios necesarios y, al final, el producto terminado.


Una historia separada es un diseño de base de datos. Desafortunadamente, el sistema contiene varios componentes CLR que no estaban bien estructurados. Las dependencias no permiten una base de implementación simple con contenido. Por el momento, esto se está resolviendo mediante un script previo a la implementación.
Además, debido al panorama desigual del sistema (las versiones de SQL Server 2008 y 2014 se instalaron en diferentes puntos), fue necesario organizar el ensamblaje del proyecto base para .Net versiones 2.0 y 4.0.


Después de que todos los scripts estaban listos y probados, se usaron en el script de compilación VSTS.


Inmediatamente antes del inicio del ensamblaje, las versiones de todos los productos se actualizaron a un número estándar común, incluido el número de compilación. El mismo número se almacenó en el script posterior a la implementación. Por lo tanto, todos los componentes, la base de datos y todas las aplicaciones cliente, salieron consistentes e igualmente numerados.


Despliegue al banco de pruebas


Una vez que se completó la versión inicial del proceso de compilación, continuamos con la preparación del script de implementación.


Se espera que la base de datos sea la más problemática.


La implementación de una copia "superior" de una base de datos real reveló muchos conflictos entre el ensamblaje y el estado de los sistemas reales:


  • Versiones inconsistentes en GIT y en el sistema real
  • Esquemas de bases de datos propiedad de usuarios que se planearon eliminar.

Estabilización del proceso de desarrollo.


Por supuesto, es extraño hablar de esto, y aún más escribir aquí, pero el cambio más serio para los desarrolladores fue la introducción del principio "si esto no está en git, esto no existe". Anteriormente, el código se comprometía "para informar al cliente". Ahora, sin esto, es imposible entregar nada.


Lo más difícil fue con el código de la base de datos. Después de cambiar a implementar la base de datos desde el repositorio, a través del ensamblaje y la implementación utilizando sqlpackage, el enfoque "delta" fue reemplazado por el enfoque "estado deseado". Los paquetes eran cosa del pasado; todo tenía que implementarse automáticamente.


Pero! Hasta la transición completa al nuevo proceso de implementación, aún era necesario entregar los cambios. Y era necesario hacer esto a la antigua usanza: "actualizaciones delta".


Nos enfrentamos a la tarea de garantizar la coherencia completa y constante del estado del sistema al entregar paquetes delta y el contenido del repositorio.


Para hacer esto, organizamos el siguiente proceso:


  1. Regularmente, el código del repositorio se recopilaba y desplegaba en una base de datos "modelo" vacía.
  2. Sobre la base de la base "modelo", se estaba preparando un autotest especial. Para cada objeto de la base de datos "modelo", se calcularon las sumas de verificación. La prueba automática contiene todas estas sumas de verificación y al inicio calcula las sumas de verificación de los objetos correspondientes de la base de datos "verificada". Cualquier discrepancia en la composición de los objetos o sus sumas de verificación conduce a una caída en la prueba.
  3. La prueba de "caída" prohibió automáticamente la transferencia de paquetes desde el entorno de prueba más abajo en el paisaje. Dicha integración ya se ha implementado en el sistema de transporte anterior.

Por lo tanto, usando el control automático, fue posible actualizar relativamente rápido el código de la base de datos del producto en git y mantenerlo sin esfuerzo adicional por parte del equipo del proyecto. Al mismo tiempo, los desarrolladores comenzaron a acostumbrarse a la necesidad de confirmar el código de manera correcta y oportuna en el repositorio.


Despliegue de productos en entornos de prueba de integración


Después de completar la etapa anterior, pasamos directamente a implementar la aplicación en un entorno de prueba. Dejamos completamente de aplicar paquetes delta a los sistemas de prueba y pasamos a la implementación automática mediante VSTS.


A partir de ese momento, todo el equipo comenzó a recibir los primeros frutos de los esfuerzos realizados anteriormente: el despliegue se llevó a cabo sin ningún esfuerzo adicional. El código personalizado se recopiló, implementó y probó automáticamente.


Desafortunadamente, como lo entendimos más tarde, la "alineación del repositorio" llevó al hecho de que teníamos una versión de la versión estable de "desarrollo", pero la versión de "producción" todavía no estaba disponible. Y por lo tanto, no había nada que ir más allá del entorno de prueba con QAS y PRD.


El código de la aplicación en el lado de la base de datos podría compararse con el productivo y comprender las diferencias. No había nada con lo que comparar las aplicaciones del cliente: solo había una versión productiva actualizada en forma de un conjunto de archivos ejecutables, y de la que se compilaron era imposible decirlo con certeza.


Probar el producto como resultado del ensamblaje automático


Después de cambiar el enfoque de ensamblaje, el producto tuvo que someterse a extensas pruebas de regresión. Era necesario asegurarse de que la aplicación funcionara y que no se perdiera nada.
Durante las pruebas, resultó ser más fácil con la funcionalidad ubicada en el costado de la base de datos. Afortunadamente, hemos desarrollado un conjunto significativo de pruebas automáticas que cubren áreas críticas.


Pero no hubo pruebas para C #, por lo tanto, todo se verificó a mano. Esta fue una cantidad significativa de trabajo, y la verificación tomó algo de tiempo.


Salto de fe: implementación productiva piloto


A pesar de las pruebas, la implementación en un producto por primera vez fue aterradora.


Tuvimos suerte: habíamos planeado el próximo despliegue del sistema en un nuevo sitio. Y decidimos usar esta oportunidad para una implementación piloto.
Los usuarios no vieron, los posibles errores del nuevo ensamblaje fueron fáciles de solucionar, el trabajo productivo real aún no ha comenzado.


Implementamos el sistema, y ​​durante varias semanas estuvo en el modo de uso preproductivo (baja carga, un cierto patrón de uso que se puede omitir en el producto). Durante este tiempo, se revelaron varios defectos que se perdieron durante las pruebas. Se corrigieron a medida que se encontraban, y la nueva versión se lanzó inmediatamente para su verificación.


Después del lanzamiento oficial y una semana de soporte posterior al lanzamiento, anunciamos que esta es la primera copia ensamblada y entregada "de una nueva manera".


Esta versión del ensamblado se convirtió en la primera versión estable de la rama maestra, se colgó con etiquetas fisrt_deployment (no pedimos iconos con un hash de la confirmación).


Despliegue a escala en todo un paisaje productivo


Como dijo James Bond: "La segunda vez es mucho más simple". Después del éxito de la implementación piloto, conectamos rápidamente las instancias restantes de sistemas de un tipo similar.


Pero el sistema tiene varios tipos de uso: una funcionalidad se puede usar para un tipo y no en otros casos. En consecuencia, la funcionalidad probada en la implementación del primer tipo no garantiza necesariamente el éxito para otros casos.


Para probar la funcionalidad de los tipos de uso restantes, comenzamos a usar proyectos activos que estaban en desarrollo. La idea era similar y la primera implementación: comenzamos a usar ensamblajes automáticos, "deslizándolos" a los usuarios junto con la funcionalidad de diseño. Por lo tanto, los usuarios, trabajando con la versión "proyecto" del producto, al mismo tiempo verificaron la funcionalidad anterior.


El escalado en sí reveló problemas técnicos inesperados:


Paisaje del sistema no homogéneo
Además de implementar directamente la aplicación, primero tuvimos que asegurarnos de que todo fuera igual en todas partes: versiones .Net, Powershell y módulos. Todo tomó una buena cantidad de tiempo.


Conexión de red
En algunos sitios, la conexión de red simplemente no permitía bombear todos los componentes del ensamblaje. Hubo tiempos de espera, daños durante la transferencia. Verificamos y probamos muchas cosas, no con mucho éxito.


Tuve que pensar en la siguiente solución: el script de ensamblaje se finalizó para que todos los resultados se empaquetaran en un gran archivo, que luego se cortó en pequeños fragmentos (2 mb cada uno). Finalizamos el escenario de implementación para eliminar la concurrencia al descargar artefactos, aceptamos los fragmentos de 2 megabytes y restauramos de ellos lo que ya se puede expandir.


Conflicto con antivirus
Otro problema extraño que encontramos es un conflicto entre el software antivirus y uno de los pasos de implementación: cuando todo tipo de archivos "sospechosos", como .js, .dll, se extraen de los archivos de artefactos, el antivirus comienza a mirarlos de cerca. Y lo más extraño es que el antivirus comienza a apresurarse al archivo antes del final del desempaquetado y el proceso de desempaquetado cae con el mensaje "el archivo está ocupado por otro proceso". Si bien estamos luchando con esto, excluir la carpeta local con artefactos del escaneo, no es muy bueno, pero no se nos ocurrió nada más.


Mejora de proceso


, " " — .


  • (service-now.com) VSTS Work Items. develop — .
  • CI feature . —
  • "self-service"
  • — . , .
  • : , CI/CD ,
  • ( )
  • - ( ) . — , .
  • VSTS , , .

Resumen



  • MS VisualStudio Team Services ( — Azure Devops) . — GIT
  • (/)
  • git / GitFlow .
  • code review .
  • CI. , feature , .
  • . , .
  • ( ) — . - .
  • - 1 . - .
  • "" .


No
1— ,6
23
35

— 14


, , , .



, — 250 * .

Source: https://habr.com/ru/post/es427111/


All Articles