
¿Cómo cambiar la arquitectura de un producto monolítico para acelerar su desarrollo y cómo dividir un equipo en varios, manteniendo la consistencia del trabajo? Para nosotros, la respuesta a estas preguntas fue la creación de una nueva API. Debajo del corte, encontrará una historia detallada sobre el camino hacia dicha solución y una descripción general de las tecnologías seleccionadas, pero para empezar, una pequeña digresión.
Hace unos años, leí en un artículo científico que se necesita más y más tiempo para una capacitación completa, y en el futuro cercano tomará ochenta años obtener conocimiento. Aparentemente, en TI este futuro ya ha llegado.
Tuve la suerte de comenzar a programar en aquellos años en que no había separación entre los programadores backend y front-end, cuando las palabras "prototipo", "ingeniero de producto", "UX" y "QA" no sonaban. El mundo era más simple, los árboles eran más altos y verdes, el aire era más limpio y los niños jugaban en los patios, en lugar de estacionar autos. No importa cómo quiera volver en ese momento, debo admitir que todo esto no es la intención del supervillano, sino el desarrollo evolutivo de la sociedad. Sí, la sociedad podría desarrollarse de manera diferente, pero, como saben, la historia no tolera el modo subjuntivo.
Antecedentes
BILLmanager apareció justo en un momento en que no había una separación rígida de direcciones. Tenía una arquitectura coherente, podía controlar el comportamiento del usuario e incluso podía ampliarse con complementos. Pasó el tiempo, el equipo desarrolló el producto y todo parecía estar bien, pero comenzaron a observarse fenómenos extraños. Por ejemplo, cuando un programador se dedicaba a la lógica de negocios, comenzó a hacer mal los formularios, haciéndolos inconvenientes y difíciles de entender. O la adición de una funcionalidad aparentemente simple tomó varias semanas: arquitectónicamente, los módulos estaban estrechamente acoplados, por lo que al cambiar uno, el otro tuvo que ser ajustado.
La conveniencia, la ergonomía y el desarrollo global de productos en general podrían olvidarse cuando la aplicación falla con un error desconocido. Si antes un programador lograba trabajar en diferentes direcciones, entonces con el crecimiento del producto y los requisitos para él, esto se hizo imposible. El desarrollador vio la imagen completa y entendió que si la función no funciona de manera correcta y estable, los formularios, botones, pruebas y promociones no ayudarán. Por lo tanto, pospuso todo y se sentó para corregir el error desafortunado. Hizo su pequeña hazaña, que no fue apreciada por nadie (simplemente no había más fuerza para la entrega correcta al cliente), pero la función comenzó a funcionar. En realidad, para que estas pequeñas hazañas lleguen a los clientes, el equipo incluirá personas responsables de diferentes áreas: frontend y backend, pruebas, diseño, soporte, promoción.
Pero ese fue solo el primer paso. El equipo ha cambiado y la arquitectura del producto se ha mantenido técnicamente estrechamente unida. Debido a esto, no fue posible desarrollar la aplicación al ritmo requerido; al cambiar la interfaz, la lógica del back-end tuvo que ser cambiada, aunque la estructura de los datos en sí a menudo permaneció sin cambios. Algo tenía que hacerse con todo esto.
Frontend y backend
Convertirse en un profesional en todo es largo y costoso, por lo tanto, el mundo moderno de los programadores aplicados se divide, en su mayor parte, en un front-end y un back-end.
Aquí parece que todo está claro: estamos reclutando programadores front-end, serán responsables de la interfaz de usuario y el backend finalmente podrá enfocarse en la lógica de negocios, modelos de datos y otros capó del motor. Al mismo tiempo, el backend, el frontend, los probadores y los diseñadores permanecerán en un solo equipo (porque hacen un producto común, solo se enfocan en diferentes partes del mismo). Estar en un equipo significa tener un espacio informativo y, preferiblemente, territorial; discutir nuevas características juntas y desarmar las terminadas; coordinar el trabajo en una gran tarea.
Para algún nuevo proyecto abstracto, esto será suficiente, pero ya teníamos la solicitud escrita, y los volúmenes del trabajo planificado y el momento de su implementación indicaban claramente que un equipo no podía hacerlo. Hay cinco personas en el equipo de baloncesto, 11 en el equipo de fútbol y teníamos unos 30. Esto no encajaba en el equipo scrum perfecto de cinco a nueve personas. Era necesario dividir, pero ¿cómo mantener la coherencia? Para moverse, era necesario resolver los problemas arquitectónicos y organizativos.

"Haremos todo en un proyecto, será más conveniente", dijeron ...
Arquitectura
Cuando un producto está desactualizado, parece lógico abandonarlo y escribir uno nuevo. Esta es una buena decisión si puede predecir el tiempo y se adaptará a todos. Pero en nuestro caso, incluso en condiciones ideales, el desarrollo de un nuevo producto llevaría años. Además, los detalles específicos de la aplicación son tales que sería extremadamente difícil cambiar de lo antiguo a lo nuevo con toda su diferencia. La compatibilidad con versiones anteriores es muy importante para nuestros clientes y, si no existe, se negarán a actualizar a la nueva versión. La viabilidad de desarrollar desde cero en este caso es dudosa. Por lo tanto, decidimos actualizar la arquitectura del producto existente manteniendo la máxima compatibilidad con versiones anteriores.
Nuestra aplicación es un monolito, cuya interfaz se creó en el lado del servidor. La interfaz solo implementó las instrucciones recibidas de ella. En otras palabras, el backend no era responsable de la interfaz de usuario . Arquitectónicamente, el front-end y el back-end funcionaron como uno, por lo tanto, al cambiar uno, nos vimos obligados a cambiar el otro. Y esto no es lo peor, lo que es mucho peor: era imposible desarrollar una interfaz de usuario sin un conocimiento profundo de lo que está sucediendo en el servidor.
Era necesario separar el front-end y el back-end, para hacer aplicaciones de software separadas: la única forma de comenzar a desarrollarlas era al ritmo y al volumen requeridos. Pero, ¿cómo hacer dos proyectos en paralelo, cambiar su estructura si dependen mucho el uno del otro?
La solución fue un sistema adicional: una capa . La idea de la capa intermedia es extremadamente simple: debe coordinar el trabajo del backend y la interfaz y asumir todos los costos adicionales. Por ejemplo, de modo que cuando la función de pago se descompone en el lado del backend, la capa combina datos y, en el lado del front-end, no es necesario cambiar nada; o para que para la conclusión en el tablero de todos los servicios ordenados por el usuario, no realicemos una función adicional en el backend, sino que agreguemos los datos en la capa.
Además de esto, la capa debía agregar certeza a lo que se puede llamar desde el servidor y que eventualmente regresará. Quería que la solicitud de operaciones fuera posible sin conocer la estructura interna de las funciones que las realizan.

Mayor estabilidad al dividir las áreas de responsabilidad.
Comunicaciones
Debido a la fuerte dependencia entre el frontend y el backend, fue imposible hacer el trabajo en paralelo, lo que ralentizó a ambas partes del equipo. Al dividir programáticamente un gran proyecto en varios, obtuvimos libertad de acción en cada uno, pero al mismo tiempo necesitábamos mantener la consistencia en el trabajo.
Alguien dirá que la consistencia se logra al mejorar las habilidades blandas. Sí, necesitan ser desarrollados, pero esto no es una panacea. Observe el tráfico, también es importante que los conductores sean educados, sepan cómo evitar obstáculos aleatorios y se ayuden mutuamente en situaciones difíciles. Pero! Sin las normas de tránsito, incluso con las mejores comunicaciones, tendríamos accidentes en cada intersección y el riesgo de no llegar al lugar a tiempo.
Necesitábamos reglas que serían difíciles de romper. Como dicen, para que sea más fácil cumplir que violar. Pero la implementación de cualquier ley conlleva no solo ventajas, sino también gastos generales, y realmente no queríamos retrasar el trabajo principal, atrayendo a todos al proceso. Por lo tanto, creamos un grupo de coordinación y luego un equipo cuyo objetivo era crear las condiciones para el desarrollo exitoso de diferentes partes del producto. Ella configuró las interfaces que permitieron que diferentes proyectos funcionen como un todo, las mismas reglas que son más fáciles de seguir que romper.
Llamamos a este comando "API", aunque la implementación técnica de la nueva API es solo una pequeña parte de sus tareas. Como las secciones comunes de código se colocan en una función separada, el equipo API analiza los problemas generales de los equipos de productos. Aquí es donde tiene lugar la conexión de nuestro frontend y backend, por lo que los miembros de este equipo deben comprender los detalles de cada dirección.
Quizás la "API" no sea el nombre más adecuado para el equipo, algo sobre arquitectura o visión a gran escala sería más adecuado, pero, creo, este problema no cambia la esencia.
API
La interfaz de acceso a funciones en el servidor existía en nuestra aplicación inicial, pero parecía caótica para el consumidor. Separar el frontend y el backend necesitaba más seguridad.
Los objetivos para la nueva API han surgido de las dificultades diarias en la implementación de nuevas ideas de productos y diseño. Necesitábamos:
- Débil conectividad de los componentes del sistema para que el backend y la interfaz puedan desarrollarse en paralelo.
- Alta escalabilidad para que la nueva API no interfiera con la funcionalidad del edificio.
- Estabilidad y consistencia.
La búsqueda de una solución para la API no comenzó con el backend, como generalmente se acepta, sino que, por el contrario, pensó lo que los usuarios necesitaban.
Los más comunes son todo tipo de API REST. En los últimos años, se les han agregado modelos descriptivos a través de herramientas como swagger, pero debe comprender que este es el mismo REST. Y, de hecho, su principal más y menos al mismo tiempo son las reglas, que son exclusivamente descriptivas. Es decir, nadie prohíbe que el creador de dicha API se desvíe de los postulados REST al implementar partes individuales.
Otra solución común es GraphQL. Tampoco es perfecto, pero a diferencia de REST, la API GraphQL no es solo un modelo descriptivo, sino reglas reales.
Anteriormente, hablé sobre el sistema, que se suponía que debía coordinar el trabajo del frontend y el backend. La capa intermedia es exactamente ese nivel intermedio. Habiendo considerado las posibles opciones para trabajar con el servidor, nos decidimos por GraphQL como una API para la interfaz . Pero, dado que el backend está escrito en C ++, la implementación del servidor GraphQL resultó ser una tarea no trivial. No describiré todas las dificultades y trucos a los que fuimos para superarlos, no produjo un resultado real. Analizamos el problema desde el otro lado y decidimos que la simplicidad es la clave del éxito. Por lo tanto, nos decidimos por soluciones probadas: un servidor Node.js separado con Express.js y Apollo Server.
Luego, tenía que decidir cómo acceder a la API de back-end. Al principio miramos en la dirección de elevar la API REST, luego intentamos usar complementos en C ++ para Node.js. Como resultado, nos dimos cuenta de que todo esto no nos convenía, y después de un análisis detallado del backend, elegimos una API basada en los servicios de gRPC .
Tras reunir la experiencia adquirida en el uso de C ++, TypeScript, GraphQL y gRPC, obtuvimos una arquitectura de aplicación que permite un desarrollo flexible del backend y la interfaz, mientras continuamos creando un único producto de software.
El resultado es un esquema en el que el front-end se comunica con un servidor intermedio mediante consultas GraphQL (sabe qué preguntar y qué obtendrá a cambio). El servidor graphQL en resolvers llama a las funciones API del servidor gRPC, y para esto usan esquemas Protobuf para la comunicación. El servidor API basado en gRPC sabe de qué microservicio tomar datos o a quién enviar la solicitud. Los microservicios en sí también se basan en gRPC, lo que garantiza la velocidad del procesamiento de consultas, la tipificación de datos y la capacidad de utilizar varios lenguajes de programación para su desarrollo.

Esquema general de trabajo después de un cambio en la arquitectura.
Este enfoque tiene una serie de desventajas, la principal de las cuales es el trabajo adicional de establecer y coordinar circuitos, así como escribir funciones auxiliares. Pero estos costos pagarán cuando haya más usuarios de API.
Resultado
Hemos recorrido el camino evolutivo del desarrollo de un producto y equipo. El éxito alcanzado o la empresa convertida en un fracaso, probablemente sea temprano para juzgar, pero los resultados intermedios pueden resumirse. Lo que tenemos ahora:
- El frontend es responsable de la pantalla y el backend es responsable de los datos.
- En el frente, la flexibilidad se mantuvo en términos de consultas y recepción de datos. La interfaz sabe qué puede preguntarle al servidor y qué respuestas debería ser.
- El backend tiene la oportunidad de cambiar el código con la confianza de que la interfaz de usuario continuará funcionando. Se hizo posible cambiar a la arquitectura de microservicios sin la necesidad de rehacer toda la interfaz.
- Ahora puede usar datos simulados para el frontend cuando el backend aún no está listo.
- La creación de esquemas de colaboración eliminó los problemas de interacción cuando los equipos entendieron la misma tarea de manera diferente. El número de iteraciones para alterar los formatos de datos se ha reducido: actuamos según el principio de "medir siete veces, cortar una vez".
- Ahora puedes planificar el trabajo de sprint en paralelo.
- Para implementar microservicios individuales, ahora puede reclutar desarrolladores que no estén familiarizados con C ++.
De todo esto, llamaría la oportunidad de desarrollar conscientemente el equipo y el proyecto como el logro principal. Creo que pudimos crear condiciones en las que cada participante puede mejorar sus competencias con mayor determinación, centrarse en las tareas y no dispersar la atención. Todos están obligados a trabajar solo en su propio sitio, y ahora es posible con una alta participación y sin cambios constantes. Es imposible convertirse en un profesional en todo, pero ahora no es necesario para nosotros .
El artículo resultó ser de revisión y muy general. Su objetivo era mostrar el camino y los resultados de una investigación compleja sobre el tema de cómo cambiar la arquitectura desde un punto de vista técnico para continuar el desarrollo del producto, así como demostrar las dificultades organizativas de dividir un equipo en partes acordadas.
Aquí me referí superficialmente a los problemas del equipo y el trabajo en equipo en un producto, la elección de la tecnología API (REST vs GraphQL), la conexión de aplicaciones Node.js con C ++, etc. Cada uno de estos temas dibuja un artículo separado, y si está interesado, entonces los escribiremos