Mas leido
Building Stories
Modo Rua: Redefiniendo el desarrollo de aplicaciones mediante iteración centrada en el usuario Ago 23
Building Stories
NuStories: Adaptación de productos para clientes fanáticos en varios países Oct 30
Culture & Values
Cómo los valores y la cultura de Nu dan forma a los productos que creamos Ago 7
Carreras
Reunimos a grandes mentes de diversos orígenes que permiten la discusión y el debate y mejoran la resolución de problemas.
Conoce más sobre nuestras carreras



Los microservicios son el estilo arquitectónico predominante en la actualidad. Para nuestro propósito, no vale la pena describir aquí lo que significa y sus ventajas, porque hay mucho contenido excelente disponible en otros lugares. En lugar de eso, vamos a ser más específicos y revisar lo que hemos aprendido al desarrollar un sistema complejo en una fintech durante los últimos seis años. Esta es una breve historia de los microservicios en Nubank.
El comienzo
Seis años, de 2013 a 2019, es también la vida útil completa de la empresa, lo que significa que comenzamos con microservicios desde el primer día, desafiando el consejo estándar de comenzar con un monolito. El razonamiento que se suele dar es que es mejor optimizar para lograr un giro rápido al principio – mientras la startup todavía busca adaptarse al mercado – para luego refactorizar a una estructura más estable. Descubrimos que era cierto que comenzar con microservicios, particularmente en 2013, nos hizo más lentos al principio. Armar una infraestructura de aprovisionamiento compleja mientras se creaba el producto desde cero fue mucho para un equipo pequeño, y tomó algún tiempo hasta que sentimos que el trabajo fluía sin problemas. Por otro lado, nuestro negocio no era muy propenso a cambios rápidos y esos primeros meses fueron probablemente el mejor momento para invertir en una base sólida, en lugar de más tarde, cuando tuvimos que enfrentar las crecientes presiones de ampliar y desarrollar el conjunto de funciones.
Eso no quiere decir que hayamos hecho todo bien desde el principio. Por el contrario, tuvimos que cambiar muchas de nuestras abstracciones centrales a medida que entendíamos el dominio con mayor profundidad, lo que en ocasiones significó que tuvimos que volver a trazar los límites del servicio. Este proceso de aprendizaje continúa hasta el día de hoy. En un momento dado, un equipo u otro estará trabajando en dividir un servicio o fusionar dos servicios.
Consulte nuestras oportunidades laborales
Aprovisionamiento e implementación
Nuestra infraestructura de producción pasó por varias iteraciones. Comenzamos sobre un servicio Chef administrado, experimentamos con CoreOS Fleet y ECS, construimos una infraestructura simple de ″un contenedor por máquina virtual″ utilizando abstracciones centrales de AWS EC2 y CloudFormation, para finalmente converger en la implementación de nuestros propios clústeres de Kubernetes. Fue un viaje largo pero mucho menos arduo debido a la automatización generalizada. Todos nuestros recursos en la nube, desde el principio, se aprovisionan mediante algún tipo de proceso automatizado. Incluso las situaciones únicas, como la instancia de livegrep que un equipo quería poner en funcionamiento rápidamente, deben automatizarse. Como la mayoría de las cosas en la empresa, nuestra obsesión por la automatización es mucho más una norma cultural que una regla impuesta.
La última encarnación de nuestra infraestructura de automatización toma la forma de una base de código Clojure que organiza la creación de recursos de AWS o Kubernetes. Una aportación fundamental a ese proceso proviene de un repositorio de git donde los ingenieros colaboran para declarar metadatos para cada microservicio: qué tipo de base de datos requiere, qué tan pesada es la carga de trabajo, qué tipo de herramientas de compilación y prueba se deben utilizar durante la compilación, etc. Con base en esos datos, nuestra automatización puede hacer magia, como aprovisionar bases de datos, configurar el descubrimiento de servicios e incluso crear canales de compilación completos para todos los servicios.
Infraestructura Inmutable
Gran parte de nuestra cultura de ingeniería está conectada con la comunidad y las ideas de programación funcional. Una idea central es el concepto de inmutabilidad: siempre es más seguro crear copias actualizadas de los datos que cambiarlos en el momento. Cuando trasladamos esta visión al dominio de la infraestructura, se traduce en crear copias actualizadas de los recursos en lugar de mutarlos en su lugar.
Una aplicación simple de este principio se aplica a la implementación: primero creamos nuevos contenedores para luego derribar las instancias antiguas. No hay nada particularmente interesante allí: un ejemplo bastante estándar de técnicas azul-verde.
Una aplicación a mayor escala es la de generar nuevas pilas de producción. De vez en cuando, tenemos que realizar cambios que van más allá de una simple implementación. Podríamos estar reforzando la seguridad, mejorando el descubrimiento de servicios o redimensionando los elementos de infraestructura. Independientemente de los detalles, el enfoque general es el mismo. Giramos una pila de producción completa (incluidos todos los servicios, clústeres de Kubernetes, clústeres de Kafka, etc.), probamos si está funcionando bien y luego dirigimos el tráfico a la nueva pila actualizando los alias DNS de todos los puntos de entrada. No hace falta decir que todo este proceso está muy automatizado, hasta el punto de que un pequeño equipo puede ejecutar todos los pasos cada dos meses.
Comunicación de servicio y mensajería asincrónica.
Cuidar las finanzas de las personas es un cargo que nos tomamos muy en serio. En consecuencia, la integridad de los datos es de suma importancia. Ejecutar una malla de microservicios manteniendo esos altos estándares plantea nuevos desafíos: ¿cómo garantizar que los datos nunca se pierdan en un mundo donde las fallas parciales y las particiones de red son la norma? Nuestra respuesta es depender en gran medida de la mensajería asincrónica.
La mayoría de las interacciones entre servicios son intermediadas por un broker de mensajería replicada confiable. En lugar de hacer que el servicio del cliente espere a que el servidor termine de procesar y responda — sujeto a todo tipo de fallas debido a desequilibrios de carga, fallas en la red y similares, — el servicio iniciador publicará de manera confiable un mensaje que luego será consumido por el siguiente servicio del flujo.
Aún pueden ocurrir fallas. Imagine que, para procesar un mensaje, el servicio al consumidor necesita realizar una llamada a un servicio de terceros que está teniendo problemas de estabilidad. Incluso entonces, podemos recuperar y evitar la pérdida de datos detectando el error en una capa inferior y redirigiendo automáticamente el mensaje a un tema de letra muerta.
Extracción de datos y toma de decisiones.
Además de los aspectos operativos que hemos cubierto hasta ahora, debemos considerar las necesidades de datos para la toma de decisiones en la empresa. Los tomadores de decisiones, incluidos los analistas de negocios y los científicos de datos, dependen de los datos que alguna vez fueron escritos por microservicios para sus modelos. Nuestro objetivo como ingenieros es ofrecerles una interfaz estable para esos datos. Esto es un desafío, dado que los modelos de datos y los límites siempre están cambiando. Además, nuestros datos están fragmentados para manejar la escalabilidad — en general, son demasiado crudos y desagradables para que nuestros homólogos analíticos trabajen con ellos.
Hace unos años, implementamos una capa llamada ″contratos″ para resolver el problema. Los contratos sirven como la interfaz estable mencionada anteriormente. Mediante contratos, podemos exponer de forma segura los datos de microservicios al lado analítico de la empresa y reducir el riesgo de romper (silenciosamente) los modelos. Los contratos son objetos de Scala generados automáticamente a partir del modelo de datos del servicio. Con los contratos, nuestros trabajos por lotes de Spark transforman los datos en tablas destinadas a cumplir cualquier propósito analítico posterior. Para asegurarnos de que esta capa refleje consistentemente la realidad (la forma de los datos), dependemos en gran medida de pruebas automatizadas — tanto para los servicios como para los trabajos de Spark.
Debido a las pruebas, rara vez nos encontramos con problemas debido a cambios en el esquema de datos ascendentes que interrumpen el análisis descendente. Sin embargo, todavía nos cuesta reaccionar adecuadamente a los cambios de los ″valores de datos″ en sentido ascendente. Esto puede suceder cuando la definición de un dato cambia (a veces de manera peligrosamente sutil) sin que el análisis posterior sea consciente de ello. Actualmente estamos atacando este problema desde varios ángulos, como la detección de anomalías en las estadísticas de la tabla (recuento, cardinalidad, etc.), la mejora de la comunicación entre las partes interesadas analíticas y de ingeniería y la desvinculación de contratos y casos de uso analíticos (implementación del almacén de datos). Sin embargo, todavía está lejos de ser un problema resuelto.
Conclusión
Hay mucho más de lo que podemos hablar. Desde otros patrones de confiabilidad que hemos aplicado a lo largo de los años hasta los detalles de nuestro viaje de orquestación de contenedores. Desde la forma en que planificamos la escalabilidad con fragmentación hasta la cultura de aprender de las interrupciones mediante autopsias irreprochables. Desde nuestras experiencias en la introducción de back-ends para nuestros front-ends hasta… bueno, ya se hace una idea. Esto es demasiado para una sola publicación de blog, pero manténgase atento a futuras notas de nuestro equipo de ingeniería.
Consulte nuestras oportunidades laborales
Descubre las oportunidades