Historia de la arquitectura Dodo IS: un monolito temprano

O toda empresa infeliz con un monolito es infeliz a su manera.

El desarrollo del sistema Dodo IS comenzó de inmediato, al igual que el negocio de Dodo Pizza, en 2011. Se basó en la idea de la digitalización completa y total de los procesos de negocio, y por mi cuenta, que incluso entonces en 2011 causó muchas preguntas y escepticismo. Pero desde hace 9 años hemos estado siguiendo este camino, con nuestro propio desarrollo, que comenzó con un monolito.

Este artículo es una "respuesta" a las preguntas "¿Por qué reescribir la arquitectura y hacer cambios a gran escala y a largo plazo?" volver al artículo anterior "Historia de la Arquitectura Dodo IS: El Camino del Back Office". Comenzaré con cómo comenzó el desarrollo de Dodo IS, cómo se veía la arquitectura original, cómo aparecieron nuevos módulos y por qué problemas hubo que hacer cambios a gran escala.

Historia de la arquitectura Dodo IS: un monolito temprano

Serie de artículos "¿Qué es Dodo IS?" habla de:

  1. Monolito temprano en Dodo IS (2011-2015). (usted está aquí)

  2. El camino de la oficina interna: bases separadas y autobús.

  3. El camino del lado del cliente: fachada sobre la base (2016-2017). (En curso...)

  4. La historia de los microservicios reales. (2018-2019). (En curso…)

  5. Aserrado terminado del monolito y estabilización de la arquitectura. (En curso...)

Arquitectura inicial

En 2011, la arquitectura Dodo IS se veía así:

Historia de la arquitectura Dodo IS: un monolito temprano

El primer módulo de la arquitectura es la aceptación de pedidos. El proceso comercial fue:

  • el cliente llama a la pizzería;

  • el gerente toma el teléfono;

  • acepta un pedido por teléfono;

  • lo completa en paralelo en la interfaz de aceptación de pedidos: tiene en cuenta información sobre el cliente, datos sobre los detalles del pedido, dirección de entrega. 

La interfaz del sistema de información se veía así...

Primera versión de octubre de 2011:

Ligeramente mejorado en enero de 2012

Dodo Pizza Sistema de Información Delivery Pizza Restaurant

Los recursos para el desarrollo del primer módulo de toma de pedidos eran limitados. Tuvimos que hacer mucho, rápido y con un equipo pequeño. Un pequeño equipo son 2 desarrolladores que sentaron las bases para todo el sistema futuro.

Su primera decisión determinó el destino de la pila de tecnología:

  • Backend en ASP.NET MVC, lenguaje C#. Los desarrolladores eran dotnetchiki, esta pila les resultaba familiar y agradable.

  • Frontend en Bootstrap y JQuery: interfaces de usuario en estilos y scripts autoescritos. 

  • Base de datos MySQL: sin costos de licencia, fácil de usar.

  • Servidores en Windows Server, porque .NET solo podría estar bajo Windows (no hablaremos de Mono).

Físicamente, todo esto se expresaba en el “dedic at the hoster”. 

Arquitectura de la aplicación de recepción de pedidos

Entonces todo el mundo ya hablaba de microservicios, y SOA se usó en grandes proyectos durante 5 años, por ejemplo, WCF se lanzó en 2006. Pero luego eligieron una solución confiable y probada.

Aqui esta

Historia de la arquitectura Dodo IS: un monolito temprano

Asp.Net MVC es Razor, que, a pedido de un formulario o de un cliente, presenta una página HTML con representación del servidor. En el cliente, los scripts CSS y JS ya muestran información y, si es necesario, realizan solicitudes AJAX a través de JQuery.

Las solicitudes en el servidor terminan en las clases *Controller, donde se lleva a cabo el procesamiento y la generación de la página HTML final en el método. Los controladores realizan solicitudes a una capa de lógica denominada *Servicios. Cada uno de los servicios correspondía a algún aspecto del negocio:

  • Por ejemplo, DepartmentStructureService dio información sobre pizzerías, sobre departamentos. Un departamento es un grupo de pizzerías dirigido por un único franquiciado.

  • ReceivingOrdersService aceptó y calculó la composición del pedido.

  • Y SmsService envió SMS llamando a los servicios API para enviar SMS.

Servicios de datos procesados ​​de la base de datos, lógica de negocios almacenada. Cada servicio tenía uno o más *Repositorios con el nombre apropiado. Ya contenían consultas a procedimientos almacenados en la base de datos y una capa de mapeadores. Había lógica comercial en los almacenamientos, especialmente mucho en aquellos que emitían informes de datos. No se usó ORM, todos confiaron en sql escrito a mano. 

También había una capa del modelo de dominio y clases auxiliares comunes, por ejemplo, la clase Order que almacenaba la orden. En el mismo lugar, en la capa, había un asistente para convertir el texto de visualización según la moneda seleccionada.

Todo esto puede ser representado por un modelo de este tipo:

Historia de la arquitectura Dodo IS: un monolito temprano

forma de pedido

Considere una forma inicial simplificada de crear dicho pedido.

Historia de la arquitectura Dodo IS: un monolito temprano

Inicialmente, el sitio era estático. Tenía precios, y encima, un número de teléfono y la inscripción "Si quieres pizza, llama al número y ordena". Para ordenar, necesitamos implementar un flujo simple: 

  • El cliente visita un sitio estático con precios, selecciona productos y llama al número que figura en el sitio.

  • El cliente nombra los productos que desea agregar al pedido.

  • Da su dirección y nombre.

  • El operador acepta el pedido.

  • El pedido se muestra en la interfaz de pedidos aceptados.

Todo comienza con la visualización del menú. Un usuario-operador registrado acepta solo un pedido a la vez. Por lo tanto, el borrador del carrito se puede almacenar en su sesión (la sesión del usuario se almacena en la memoria). Hay un objeto de carrito que contiene productos e información del cliente.

El cliente nombra el producto, el operador hace clic en + junto al producto y se envía una solicitud al servidor. La información sobre el producto se extrae de la base de datos y la información sobre el producto se agrega al carrito.

Historia de la arquitectura Dodo IS: un monolito temprano

Nota. Sí, aquí no puede extraer el producto de la base de datos, sino transferirlo desde la interfaz. Pero para mayor claridad, mostré exactamente la ruta desde la base de datos. 

A continuación, ingrese la dirección y el nombre del cliente. 

Historia de la arquitectura Dodo IS: un monolito temprano

Al hacer clic en "Crear pedido":

  • La solicitud se envía a OrderController.SaveOrder().

  • Obtenemos carrito de la sesión, hay productos en la cantidad que necesitamos.

  • Complementamos el Cart con información sobre el cliente y lo pasamos al método AddOrder de la clase ReceivingOrderService, donde se guarda en la base de datos. 

  • La base de datos tiene tablas con el pedido, la composición del pedido, el cliente, y todos están conectados.

  • La interfaz de visualización de pedidos extrae los últimos pedidos y los muestra.

Nuevos módulos

Tomar el pedido era importante y necesario. No puedes hacer un negocio de pizzas si no tienes una orden para vender. Por lo tanto, el sistema comenzó a adquirir funcionalidad, aproximadamente desde 2012 hasta 2015. Durante este tiempo, aparecieron muchos bloques diferentes del sistema, que llamaré módulos, en oposición al concepto de servicio o producto. 

Un módulo es un conjunto de funciones que están unidas por algún objetivo comercial común. Al mismo tiempo, están físicamente en la misma aplicación.

Los módulos se pueden llamar bloques de sistema. Por ejemplo, este es un módulo de informes, interfaces de administración, rastreador de alimentos en la cocina, autorización. Todas estas son interfaces de usuario diferentes, algunas incluso tienen diferentes estilos visuales. Al mismo tiempo, todo está dentro del marco de una aplicación, un proceso en ejecución. 

Técnicamente, los módulos fueron diseñados como Área (tal idea incluso se mantuvo en núcleo de asp.net). Había archivos separados para la interfaz, los modelos, así como sus propias clases de controlador. Como resultado, el sistema se transformó de este ...

Historia de la arquitectura Dodo IS: un monolito temprano

...dentro de esto:

Historia de la arquitectura Dodo IS: un monolito temprano

Algunos módulos son implementados por sitios separados (proyecto ejecutable), debido a una funcionalidad completamente separada y en parte debido a un desarrollo ligeramente separado y más enfocado. Este:

  • Planta - primera versión sitio dodopizza.ru.

  • Exportar: carga de informes de Dodo IS para 1C. 

  • Personal - cuenta personal del empleado. Fue desarrollado por separado y tiene su propio punto de entrada y diseño separado.

  • fs — un proyecto para alojar estáticas. Más tarde nos alejamos de él, trasladando todas las estáticas a la CDN de Akamai. 

El resto de los bloques estaban en la aplicación BackOffice. 

Historia de la arquitectura Dodo IS: un monolito temprano

Explicación del nombre:

  • Cajero - Cajero de restaurante.

  • ShiftManager: interfaces para el rol de "Gerente de turno": estadísticas operativas sobre las ventas de pizzerías, la capacidad de poner productos en la lista de parada, cambiar el orden.

  • OfficeManager: interfaces para los roles de "Gerente de pizzería" y "Franquiciado". Aquí se recopilan funciones para configurar una pizzería, sus promociones de bonificación, recibir y trabajar con empleados, informes.

  • PublicScreens: interfaces para televisores y tabletas colgadas en pizzerías. Los televisores muestran menús, información publicitaria, estado del pedido en el momento de la entrega. 

Utilizaron una capa de servicio común, un bloque de clase de dominio Dodo.Core común y una base común. A veces, aún podían conducirse a lo largo de las transiciones entre sí. La inclusión de sitios individuales, como dodopizza.ru o personal.dodopizza.ru, se destinó a servicios generales.

Cuando aparecieron nuevos módulos, intentamos reutilizar al máximo el código ya creado de servicios, procedimientos almacenados y tablas en la base de datos. 

Para una mejor comprensión de la escala de los módulos hechos en el sistema, aquí hay un diagrama de 2012 con planes de desarrollo:

Historia de la arquitectura Dodo IS: un monolito temprano

Para 2015, todo estaba en el mapa y aún más en producción.

  • La aceptación de pedidos se ha convertido en un bloque separado del Contact Center, donde el operador acepta el pedido.

  • Había pantallas públicas con menús e información colgadas en pizzerías.

  • La cocina tiene un módulo que reproduce automáticamente el mensaje de voz "Nueva Pizza" cuando llega un nuevo pedido, y también imprime una factura para el mensajero. Esto simplifica enormemente los procesos en la cocina, permite que los empleados no se distraigan con una gran cantidad de operaciones simples.

  • La unidad de entrega se convirtió en un Delivery Checkout independiente, donde se emitía el pedido al mensajero que había ocupado previamente el turno. Su tiempo de trabajo se tuvo en cuenta para el cálculo de la nómina. 

Paralelamente, de 2012 a 2015, aparecieron más de 10 desarrolladores, abrieron 35 pizzerías, implementaron el sistema en Rumania y se prepararon para la apertura de puntos de venta en los Estados Unidos. Los desarrolladores ya no se ocuparon de todas las tareas, sino que se dividieron en equipos. cada uno especializado en su propia parte del sistema. 

Problemas

Incluso por la arquitectura (pero no solo).

Caos en la base

Una base es conveniente. La consistencia se puede lograr en él y a expensas de las herramientas integradas en las bases de datos relacionales. Trabajar con él es familiar y conveniente, especialmente si hay pocas tablas y pocos datos.

Pero después de 4 años de desarrollo, la base de datos resultó tener alrededor de 600 tablas, 1500 procedimientos almacenados, muchos de los cuales también tenían lógica. Por desgracia, los procedimientos almacenados no brindan muchas ventajas cuando se trabaja con MySQL. La base no los almacena en caché, y almacenar lógica en ellos complica el desarrollo y la depuración. La reutilización de código también es difícil.

Muchas tablas no tenían índices adecuados, en algún lugar, por el contrario, había muchos índices, lo que dificultaba la inserción. Fue necesario modificar alrededor de 20 tablas: la transacción para crear un pedido podría demorar entre 3 y 5 segundos. 

Los datos en las tablas no siempre estaban en la forma más apropiada. En algún lugar era necesario hacer la desnormalización. Parte de los datos recibidos regularmente estaban en una columna en forma de estructura XML, esto aumentaba el tiempo de ejecución, alargaba las consultas y complicaba el desarrollo.

Para las mismas tablas se produjeron muy solicitudes heterogéneas. Las mesas populares sufrieron especialmente, como la mesa mencionada anteriormente. en pedidos de venta. o mesas pizzería. Se utilizaron para mostrar interfaces operativas en la cocina, análisis. Otro sitio los contactó (dodopizza.ru), donde en un momento dado podrían llegar de repente una gran cantidad de solicitudes. 

Los datos no fueron agregados. y muchos cálculos se realizaron sobre la marcha utilizando la base. Esto creó cálculos innecesarios y una carga adicional. 

A menudo, el código iba a la base de datos cuando no podía haberlo hecho. En algún lugar no había suficientes operaciones masivas, en algún lugar sería necesario distribuir una solicitud en varias a través del código para acelerar y aumentar la confiabilidad. 

Cohesión y ofuscación en el código

Los módulos que se suponía que eran responsables de su parte del negocio no lo hicieron con honestidad. Algunos de ellos tenían duplicación de funciones por roles. Por ejemplo, un vendedor local que es responsable de la actividad de marketing de la red en su ciudad tuvo que usar tanto la interfaz "Administrador" (para crear promociones) como la interfaz "Administrador de oficina" (para ver el impacto de las promociones en el negocio). Por supuesto, dentro de ambos módulos se utilizaba el mismo servicio que funcionaba con promociones de bonificación.

Los servicios (clases dentro de un gran proyecto monolítico) podrían llamarse entre sí para enriquecer sus datos.

Con las propias clases modelo que almacenan datos, el trabajo en el código se llevó a cabo de manera diferente. En algún lugar había constructores a través de los cuales era posible especificar campos obligatorios. En algún lugar esto se hizo a través de propiedades públicas. Por supuesto, la obtención y transformación de datos de la base de datos fue variada. 

La lógica estaba en los controladores o en las clases de servicio. 

Estos parecen ser problemas menores, pero ralentizaron en gran medida el desarrollo y redujeron la calidad, lo que provocó inestabilidad y errores. 

La complejidad de un gran desarrollo

Las dificultades surgieron en el desarrollo mismo.. Era necesario hacer diferentes bloques del sistema, y ​​en paralelo. Encajar las necesidades de cada componente en un solo código se hizo cada vez más difícil. No fue fácil estar de acuerdo y complacer a todos los componentes al mismo tiempo. A esto se sumaron las limitaciones en la tecnología, especialmente en lo que respecta a la base y la interfaz. Era necesario abandonar jQuery hacia frameworks de alto nivel, especialmente en términos de servicios al cliente (sitio web).

En algunas partes del sistema, se podrían utilizar bases de datos más adecuadas para esto.. Por ejemplo, más adelante tuvimos el caso de uso de pasar de Redis a CosmosDB para almacenar una cesta de pedidos. 

Los equipos y desarrolladores involucrados en su campo claramente querían más autonomía para sus servicios, tanto en términos de desarrollo como de implementación. Combinar conflictos, liberar problemas. Si para 5 desarrolladores este problema es insignificante, entonces con 10, y aún más con el crecimiento planificado, todo se volverá más serio. Y adelante iba a estar el desarrollo de una aplicación móvil (comenzó en 2017, y en 2018 fue gran caída). 

Diferentes partes del sistema requerían diferentes niveles de estabilidad., pero debido a la fuerte conectividad del sistema, no pudimos proporcionar esto. Un error en el desarrollo de una nueva función en el panel de administración bien podría haber ocurrido en la aceptación de un pedido en el sitio, porque el código es común y reutilizable, la base de datos y los datos también son los mismos.

Probablemente sería posible evitar estos errores y problemas en el marco de una arquitectura modular monolítica de este tipo: dividir la responsabilidad, refactorizar tanto el código como la base de datos, separar claramente las capas entre sí, controlar la calidad todos los días. Pero las soluciones arquitectónicas elegidas y el enfoque en la rápida expansión de la funcionalidad del sistema generaron problemas en términos de estabilidad.

Cómo el blog Power of the Mind colocó las cajas registradoras en los restaurantes

Si el crecimiento de la red de pizzerías (y la carga) continuara al mismo ritmo, luego de un tiempo las caídas serían tales que el sistema no subiría. Bien ilustra los problemas que comenzamos a enfrentar en 2015, aquí hay una historia así. 

En el blog"Poder de la mente” era un widget que mostraba datos sobre los ingresos del año de toda la red. El widget accedió a la API pública de Dodo, que proporciona estos datos. Esta estadística está disponible actualmente en http://dodopizzastory.com/. El widget se mostró en cada página y realizó solicitudes en un temporizador cada 20 segundos. La solicitud fue a api.dodopizza.ru y solicitó:

  • el número de pizzerías en la red;

  • ingresos totales de la red desde el comienzo del año;

  • ingresos de hoy.

La solicitud de estadísticas sobre ingresos fue directamente a la base de datos y comenzó a solicitar datos sobre pedidos, agregando datos sobre la marcha y entregando la cantidad. 

Las cajas de los restaurantes acudían a la misma mesa de pedidos, descargaban una lista de pedidos recibidos para el día de hoy, y se añadían nuevos pedidos. Las cajas registradoras hicieron sus solicitudes cada 5 segundos o en la actualización de la página.

El diagrama se veía así:

Historia de la arquitectura Dodo IS: un monolito temprano

Un otoño, Fyodor Ovchinnikov escribió un artículo largo y popular en su blog. Mucha gente vino al blog y empezó a leer todo detenidamente. Mientras cada una de las personas que acudían leía el artículo, el widget de ingresos funcionaba correctamente y solicitaba la API cada 20 segundos.

La API llamó a un procedimiento almacenado para calcular la suma de todos los pedidos desde el comienzo del año para todas las pizzerías de la red. La agregación se basó en la tabla de pedidos, que es muy popular. A ella acuden todas las cajas de todos los restaurantes abiertos en ese momento. Las cajas dejaron de responder, no se aceptaron pedidos. Tampoco fueron aceptados del sitio, no aparecieron en el rastreador, el gerente de turno no pudo verlos en su interfaz. 

Esta no es la única historia. Para el otoño de 2015, todos los viernes la carga del sistema era crítica. Varias veces apagamos la API pública, y una vez, incluso tuvimos que apagar el sitio, porque nada ayudó. Incluso había una lista de servicios con una orden de cierre bajo cargas pesadas.

A partir de ahora comienza nuestra lucha con las cargas y por la estabilización del sistema (de otoño de 2015 a otoño de 2018). Fue entonces cuando sucedió"gran caída". Además, a veces también ocurrieron fallas, algunas fueron muy sensibles, pero el período general de inestabilidad ahora puede considerarse superado.

Rápido crecimiento empresarial

¿Por qué no se pudo hacer de inmediato? Basta con mirar los siguientes gráficos.

Historia de la arquitectura Dodo IS: un monolito temprano

También en 2014-2015 hubo una apertura en Rumania y se estaba preparando una apertura en los EE. UU.

La red creció muy rápido, se abrieron nuevos países, aparecieron nuevos formatos de pizzerías, por ejemplo, se abrió una pizzería en el patio de comidas. Todo esto requirió una atención significativa específicamente a la expansión de las funciones de Dodo IS. Sin todas estas funciones, sin el seguimiento en la cocina, la contabilidad de productos y pérdidas en el sistema, la visualización de la emisión de una orden en el patio de comidas, difícilmente estaríamos hablando de la arquitectura "correcta" y el enfoque "correcto" para desarrollo ahora.

Otro obstáculo para la revisión oportuna de la arquitectura y en general la atención de los problemas técnicos fue la crisis de 2014. Cosas como esta golpean fuertemente las oportunidades de crecimiento de los equipos, especialmente para un negocio joven como Dodo Pizza.

Soluciones rápidas que ayudaron

Problemas que necesitaban soluciones. Convencionalmente, las soluciones se pueden dividir en 2 grupos:

  • Unos rápidos que apagan el fuego y dan un pequeño margen de seguridad y nos dan tiempo para cambiar.

  • Sistémica y, por tanto, prolongada. Reingeniería de una serie de módulos, división de una arquitectura monolítica en servicios separados (la mayoría de ellos no son en absoluto micro, sino macro servicios, y hay algo al respecto El informe de Andrey Morevskiy). 

La lista seca de cambios rápidos es la siguiente:

Maestro base de escalado vertical

Eso sí, lo primero que se hace para hacer frente a las cargas es aumentar la capacidad del servidor. Esto se hizo para la base de datos maestra y para los servidores web. Por desgracia, esto es posible solo hasta cierto límite, luego se vuelve demasiado costoso.

Desde 2014, nos mudamos a Azure, también escribimos sobre este tema en ese momento en el artículo “Cómo Dodo Pizza entrega pizza usando la nube de Microsoft Azure". Pero después de una serie de aumentos en el servidor para la base, se encontraron con el costo. 

Réplicas base para lectura

Se hicieron dos réplicas para la base:

Leer réplica para solicitudes de referencia. Se utiliza para leer directorios, tipo, ciudad, calle, pizzería, productos (dominio cambiado lentamente), y en aquellas interfaces donde un pequeño retraso es aceptable. Hubo 2 de estas réplicas, aseguramos su disponibilidad de la misma manera que los maestros.

ReadReplica para solicitudes de informes. Esta base de datos tenía menor disponibilidad, pero todos los informes iban a ella. Permita que tengan grandes solicitudes de recálculos de datos enormes, pero que no afecten a la base de datos principal ni a las interfaces operativas. 

Cachés en código

No había cachés en ninguna parte del código (en absoluto). Esto condujo a solicitudes adicionales, no siempre necesarias, a la base de datos cargada. Primero, los cachés estaban tanto en la memoria como en un servicio de caché externo, que era Redis. Todo fue invalidado por el tiempo, la configuración se especificó en el código.

Múltiples servidores back-end

También era necesario escalar el backend de la aplicación para manejar el aumento de las cargas de trabajo. Era necesario hacer un clúster a partir de un servidor iis. hemos reprogramado sesión de aplicación de la memoria a RedisCache, lo que permitió hacer varios servidores detrás de un balanceador de carga simple con round robin. Al principio, se usaba el mismo Redis que para los cachés, luego se dividió en varios. 

Como resultado, la arquitectura se ha vuelto más complicada...

Historia de la arquitectura Dodo IS: un monolito temprano

… pero parte de la tensión se eliminó.

Y luego fue necesario rehacer los componentes cargados, que emprendimos. Hablaremos de esto en la siguiente parte.

Fuente: habr.com

Añadir un comentario