El crecimiento de microservice adopción ha provocado un resurgimiento de la popularidad de algunos previamente se pasa por alto, patrones de diseño software. Muchos de estos patrones han sido extraídos del Diseño impulsado por dominios de Eric Evans, un libro que trata tanto de la estructura del equipo como de la arquitectura de software.
Y de estos patrones, el Contexto Delimitado es quizás el más importante de entender. Como ingenieros, hemos llegado a considerar el Contexto Limitado como un patrón de diseño de arquitectura de software. Pero eso es porque lo hemos tomado un poco de su uso original. Tal como lo utiliza Evans, el Contexto Delimitado es tanto un patrón organizativo como técnico.
Es por eso que he llegado a ver el patrón de contexto delimitado como un eje en la comprensión de los microservicios. No solo cómo construirlos, sino realmente por qué los construimos en primer lugar, y cómo pueden hacer que nuestras organizaciones sean más exitosas. Si entendemos lo que son los Contextos Limitados, si adoptamos la mentalidad de Contexto Limitado tanto técnica como organizativamente, entonces podemos tener éxito en la construcción de nuestra arquitectura de microservicios.
¿Por qué migrar a microservicios?
Para empezar, hagamos un poco de ejercicio. Hágase esta pregunta: ¿Por qué construimos microservicios en primer lugar?
Tómese un momento para pensarlo. ¿Cuáles son los beneficios que primero vienen a la mente? ¿Cuáles son los principales problemas que debemos esperar resolver? Anota algunas respuestas, solo para ser honesto.
…
¿tienes tu respuesta? Bien. Léelo para ti mismo. ¿Conseguiste los beneficios técnicos estándar? ¿Entrega continua, escalabilidad, entornos políglotas, contenedores y nubes, y todo eso? Gran.
Pero, ¿su respuesta principal incluyó algo sobre permitir que su organización opere de manera más eficiente? Debería. Porque crear microservicios no se trata de obtener beneficios técnicos. En realidad, se trata de obtener beneficios organizacionales. Todo lo demás es un detalle de implementación.
Monolitos = código acoplado y equipos acoplados
A medida que nuestros monolitos crecen y crecen, la productividad comienza a disminuir. Hay al menos dos razones principales para ello.
Frenando nuestra velocidad
En primer lugar, cada equipo de ingeniería está contribuyendo a una base de código gigante. Como tal, los equipos se enfrentan a una probabilidad cada vez mayor de que su código entre en conflicto con el código de otros. Para ayudar a mitigar los posibles problemas que esto podría causar, instituimos procedimientos — congelaciones de código, períodos de prueba de control de calidad, trenes de liberación, etc. – que están literalmente diseñados para ralentizar nuestra productividad.
Por supuesto, estos procedimientos evitan que las características y mejoras se implementen de manera oportuna. También causan estragos en la capacidad de los ingenieros para centrarse en las prioridades de sus equipos. Si se encuentra un error durante un período de prueba, el equipo responsable debe cambiar de contexto y centrarse en resolver ese error. Si se encuentra un error grave en producción, el equipo no solo tiene que corregirlo, sino también saltar a través de aros para implementarlo en el próximo tren de lanzamiento.
El servicio de guardia se convierte en un despilfarro. Si algo sale mal con nuestro monolito, alguien tiene que estar disponible, de día o de noche, para resolver el problema. ¿Pero quién? Las organizaciones grandes con monolitos grandes generalmente se enfrentan a dos opciones:
Un equipo de gestión de incidentes cuyo único y triste trabajo dentro de la organización es responder a los problemas causados por el código de otros ingenieros y descubrir cómo resolverlos.
Un horario de guardia rotativo, en el que cada semana a un ingeniero arbitrario se le asigna el triste y lamentable trabajo de convertirse en responsable de resolver problemas que probablemente sean causados por código escrito por algún otro ingeniero, en algún otro equipo de ingeniería.
(Mal)organizar nuestros equipos
Los monolitos se meten con nuestras organizaciones de otra manera. Toda nuestra organización está trabajando en el mismo producto grande. Pero aún tenemos que dividir la organización en equipos manejables. Así que tendemos a buscar roles funcionales para encontrar límites de equipo:
por Desgracia, este tipo de estructura organizativa de los límites de trabajo colaborativo. En lugar de trabajar juntos para resolver el verdadero problema que tenemos entre manos (por ejemplo, ¿cómo diseñamos, construimos y mantenemos la característica X?) los miembros de las diferentes áreas funcionales simplemente se enfocan en su propia parte, metafóricamente lanzando su trabajo por encima de la valla cuando han terminado. Se pierde el potencial de colaboración y sinergia, donde la calidad combinada del esfuerzo del equipo es mucho más que la suma de los miembros individuales del equipo.
También está lleno de cuellos de botella. Cuando organizamos nuestros equipos por áreas funcionales, naturalmente tendremos una desalineación en las prioridades. Digamos que el equipo de gestión de productos decidió que el proceso de pago de nuestro monolith necesita ser renovado. Programarán tiempo con el equipo de diseño para armar algunas burlas. En algún momento, las burlas se terminarán y se entregarán al equipo de frontend para implementarlas. Por supuesto, el equipo de frontend necesitará que las API sean implementadas por el equipo de backend, por lo que se bloquearán hasta que se complete. Una vez que el equipo de backend prioriza su trabajo en los nuevos servicios de pago, descubre que necesita ayuda del equipo de Administración de Bases de datos (DBA). Que, por supuesto, tiene sus propias prioridades. Por lo tanto, el equipo de backend se bloqueará hasta que se libere un DBA.
En un camino, esta estructura organizativa parece un poco como un mal diseñada, excesivamente junto arquitectura de software… ¿no?
Por el contrario, una arquitectura de microservicios permite la autonomía del equipo. Se hace mucho más fácil formar equipos que sean autónomos, que trabajen juntos de manera eficiente y que no estén bloqueados constantemente por dependencias de otros equipos.
Los equipos pueden hacerse cargo de su trabajo, desde el diseño hasta el desarrollo y la implementación. Cada miembro comparte la responsabilidad de lograr el objetivo de su equipo, por lo que se les incentiva a participar en algo más que «su parte». He trabajado con equipos en los que los gerentes de producto, diseñadores, ingenieros de front-end, back-end e ingenieros móviles se han unido para diseñar características de productos, lo que produce resultados mucho mejores que los que podría haber logrado una sola persona.
El equipo adquiere la responsabilidad de sus propios artefactos una vez que se implementan en producción. Esto generalmente conduce a un código de mayor calidad que es más fácil de solucionar. ¿Por qué es eso? A diferencia de un monolito, los equipos tienden a tener una visión holística de los microservicios que poseen. Por lo tanto, es mucho más fácil para el equipo anticipar problemas, agregar buenos registros y métricas para solucionar problemas cuando ocurren, y hacer un uso adecuado de los patrones de resiliencia (por ejemplo, reintentos, disyuntores y fallas, etc.) para ayudar a evitar problemas en primer lugar.
Además, dado que los equipos tienen un sentido de propiedad total sobre su trabajo, mantener sus servicios saludables y funcionando en producción se convierte menos en un calendario de lanzamientos de pesadilla, y más en fomentar su creación.
Finalmente, los equipos están trabajando hacia el mismo objetivo, en la misma línea de tiempo. Eso significa que ya no bloqueará a una persona mientras espera a que alguien en otra área funcional se libere.
Tenemos que ser intencionales con respecto a la autonomía
Pero no obtenemos estos beneficios de forma gratuita simplemente dividiendo nuestro monolito en microservicios. Echemos un vistazo a nuestro primer, ingenuo punto de vista de un microservices arquitectura:
Si somos como la mayoría de los ingenieros, nuestra idea inicial de un microservice la arquitectura es, bueno, un montón de microservices. Cada uno expone algún tipo de API (ReST, quizás) para permitir que cualquier otro servicio lea y escriba en él.
A medida que ganamos experiencia, aprendemos que no todos los microservicios sirven para el mismo propósito, o, al menos, no deberían. Y por lo tanto, al igual que nuestro monolito habían sido dispuestas en capas, así que organizamos nuestro microservices:
En este punto, hemos definido los diferentes tipos de microservices y las aplicaciones que queremos construir. Gran. Pero todavía no hemos avanzado mucho en términos de autonomía del equipo. Cada microservicio tendrá que ser propiedad de algún equipo. Y así surge la pregunta: ¿qué equipos poseerán qué microservicios?
Equipos funcionales cruzados
Nuestro primer enfoque ingenuo podría ser organizar nuestros equipos imitando nuestra estructura de organización monolítica: