Imagina que lanzas una aplicación que conecta personas que van a preparar la cena con personas que no tienen ganas de cocinar hoy, pero que pueden pagar una cantidad justa por una comida casera recién preparada. Al principio, utilizas tu computadora personal y un DNS dinámico para direccionar tráfico hacia ella para resolver las solicitudes que se le hacen a la aplicación. Todo funciona bien y se convierte en un éxito en unos cuantos meses. De repente, el tiempo de respuesta empieza a disminuir y los usuarios comienzan a quejarse del desempeño de la aplicación. Es hora de crecer. ¿Qué hacer para resolver esto? Aun si compras una computadora con mejor procesador y más memoria, esta situación se puede repetir cuando el negocio crezca más. Felicidades, te acabas de topar con el problema feliz de la escalabilidad. Aquí es donde conocer acerca de la nube y la arquitectura de microservicios te puede ayudar.
Los microservicios, también conocidos como arquitectura de microservicios, son un estilo arquitectónico que estructura una aplicación como una colección de servicios, tales como:
La arquitectura de microservicio permite la entrega rápida, frecuente y confiable de aplicaciones grandes y complejas.
Microservicios
Una arquitectura de microservicios es una colección de servicios autónomos, pequeños y de propósito definido. Cada servicio es independiente y debe implementar una funcionalidad de negocio específica dentro de un contexto delimitado. Es decir, los límites, actividades y datos afectados por el servicio son fácilmente identificables y de alcance discernible.
Cada servicio en una arquitectura de microservicios se puede desarrollar, implementar, operar y escalar de forma independiente de los demás servicios utilizados en la organización.
Los servicios no necesitan compartir ninguno de sus códigos, frameworks, lenguajes, implementaciones o repositorios de datos con otros servicios.
Los servicios son los responsables de conservar sus propios datos. Esto difiere del modelo tradicional, donde una capa de datos independiente controla la persistencia de los mismos.
Cualquier comunicación entre componentes individuales ocurre a través de application program interfaz (API) bien definidas.
Los beneficios de los microservicios son los siguientes:
Las complicaciones de los microservicios son los siguientes:
Los microservicios son una forma de trabajar con aplicaciones grandes de manera modular y con esto atender grandes demandas que son exigentes en cuestiones de cambios, capacidades y disponibilidad. Se puede decir que los microservicios son un conjunto de tareas de la arquitectura orientada a servicios.
La evolución de las arquitecturas de servicios
La característica principal de las aplicaciones monolíticas es que hacen uso de una base de código única para sus servicios y funcionalidades. Es decir, la aplicación tiene control absoluto sobre todas las tareas necesarias para realizar una determinada función.
Este tipo de aplicaciones sobresalen por combinar la interfaz del usuario y la capa de acceso a los datos en el mismo programa y alojarlos en el mismo lugar.
Su principal cometido de la aplicación distribuida es tener la aplicación disgregada en múltiples nodos, dando la posibilidad de que en cada nodo exista un componente de la aplicación o un mismo componente replicado en varios nodos.
Esta arquitectura es bastante más compleja y difícil de gestionar y administrar que una aplicación monolítica.
Uno de los grandes conceptos que introduce este tipo de aplicaciones es el escalado horizontal. Es decir, cuando por cuestiones de demanda los recursos actuales ya no son suficientes, en vez de aumentar los recursos de un nodo (escalado vertical), lo que se hace es incrementar el número de nodos dedicados al procesamiento de ese componente demandado.
Otro gran concepto que surge, aunque todavía no de forma automática y madura, es la llamada elasticidad. Es decir, al igual que se aumentan el número de nodos dedicados para soportar un aumento de demanda, se pueden reducir si la demanda disminuye y dejarlos disponibles para otros procesos.
La seguridad y la robustez mejoran notablemente, ya que es más probable que los problemas de seguridad de un nodo sean cercados en ese nodo.
La arquitectura SOA fue la que insistió en la utilización de aplicaciones distribuidas y orientadas a servicios.
La idea fue hacer que esos servicios fueran independientes y las interacciones entre ellos se hicieran bajo ciertos protocolos estandarizados de comunicación, como WSDL y SOAP.
Esta independencia hacía posible que servicios desarrollados por diferentes tecnologías pudiesen comunicarse entre sí, sin ningún problema.
La arquitectura SOA está más enfocada a la arquitectura de la aplicación. Cloud Native se enfoca más en la arquitectura del sistema que alberga, distribuye y ofrece las aplicaciones.
Este punto de vista ofrece básicamente el uso de recursos en la nube bajo demanda automatizada. Es decir, va a existir una elasticidad gestionada de forma automática por el sistema en la infraestructura dedicada a cada una de las aplicaciones que distribuyen.
No existe una definición formal, pero básicamente es una evolución de SOA (service oriented architecture) y está orientada al trabajo con servicios muy pequeños e independientes.
El objetivo es aislar los distintos componentes de una aplicación con el fin de que cada uno sea una aplicación por sí misma.
Otra de las diferencias con SOA es la comunicación entre los servicios; ya no sería con servicios web WSDL o SOAP, sino vía HTTP con API-REST.
Cómo modelar servicios
Puede resultar útil establecer algunos estándares para todo el proyecto sin restringir excesivamente la flexibilidad de los equipos. Esto se aplica especialmente a las funcionalidades transversales, como el registro.
El diseño de las API requiere atención al detalle. Debemos evitar que las API se comuniquen demasiado, pensando en formatos de serialización y buscando lugares para utilizar patrones de comunicación asincrónica como la nivelación de carga basada en buffers.
Una práctica usual en el diseño de microservicios es utilizar la metodología DDD (domain driven design), la cual consta de cuatro pasos:
Entidades. Una entidad es un objeto con una identidad única que persiste en el tiempo.
Una entidad tiene un identificador único en el sistema, que se puede usar para buscar la entidad o para recuperarla. Una identidad puede abarcar varios contextos delimitados y puede durar más que la aplicación. Los atributos de una entidad pueden cambiar con el tiempo. Una entidad puede contener referencias a otras entidades.
Objetos de valor. Un objeto de valor no tiene identidad. Se define únicamente mediante los valores de sus atributos. Los objetos de valor también son inmutables. Para actualizar un objeto de valor, siempre hay que crear una nueva instancia que reemplace a la anterior.
Agregados. Un agregado define un límite de coherencia alrededor de una o varias entidades. Una entidad exacta en un agregado es la raíz. La búsqueda se realiza con el identificador de la entidad raíz. Cualquier otra entidad en el agregado es secundaria de la raíz y hace referencia a ella siguiendo apuntadores desde esta. Si la aplicación modifica varios objetos relacionados, ¿cómo podemos garantizar la coherencia? ¿Cómo se realiza el seguimiento de los elementos invariables y cómo se aplican?
Partiendo el monolito
Al tratar de transformar una arquitectura monolítica a una arquitectura de microservicios es importante revisar detenidamente el entorno que se tiene adelante ya que, al momento de partir el monolito en partes pequeñas, que son los microservicios, se tendrá el desafío de establecer los límites de cada uno sin olvidar las responsabilidades que esto conlleva.
Aunque el proceso para determinar entidades es muy similar al que se seguiría para una aplicación monolítica, hay que buscar formas de que los datos de cada servicio sean auto-contenidos y auto-gestionados por cada servicio. Si otro servicio requiere hacer consultar y/o modificar atributos de una entidad, esto debe de hacerse a través de métodos provistos por el API que gestiona el dominio del servicio y no acceder a los datos directamente. La intención es mantener los servicios lo menos interrelacionados posible para que de esa forma se vuelvan fácilmente escalables y que no requieran validaciones por otros equipos para ser modificados, siempre y cuando mantengan una interfaz consistente a través del API.
La arquitectura de monolito es considerada como el método tradicional de construir las cosas. Una aplicación monolítica se construye como una programa único e indivisible. Típicamente una aplicación de este tipo está compuesta por las siguientes capas:
Las fortalezas de la arquitectura monolítica son las siguientes:
Las debilidades de la arquitectura monolítica son las siguientes:
La metodología de aplicación (app) con 12 factores
En la era moderna, el software se entrega comúnmente como un servicio: llamadas aplicaciones web o software como servicio. La aplicación de 12 factores es una metodología para crear aplicaciones de software como servicio que:
Hoy en día el software se está distribuyendo como software as a service (SaaS) o, como se le conoce comúnmente, aplicaciones web o web “apps”. La metodología de los 12 factores se puede aplicar a aplicaciones escritas en cualquier lenguaje de programación y que utilizan cualquier combinación de servicios de respaldo (base de datos, Queue, caché de memoria, etc.).
A continuación, se presentan las características de cada uno de ellos.
El código base es el mismo en todas las implementaciones, aunque pueden estar activas diferentes versiones en cada implementación. Por ejemplo, un desarrollador tiene algunas commits que aún no se han implementado en el servidor de staging. La versión liberada tiene algunos commits que aún no se han implementado en producción, pero todos comparten la misma base de código, lo que los hace identificables como diferentes implementaciones de la misma aplicación.
Las aplicaciones a veces almacenan la configuración como constantes en el código. Esta es una violación de los 12 factores que requiere una separación estricta de la configuración del código. La configuración varía sustancialmente entre las implementaciones; el código, no.
La aplicación de 12 factores almacena la configuración en variables de entorno. Las variables de entorno son fáciles de cambiar entre implementaciones sin cambiar ningún código; a diferencia de los archivos de configuración, hay pocas posibilidades de que se registren accidentalmente en el repositorio de código. Y a diferencia de los archivos de configuración personalizados u otros mecanismos de configuración como las propiedades del sistema Java, son un estándar independiente del lenguaje y del sistema operativo.
La aplicación de 12 factores utiliza una separación estricta entre las etapas de compilación, lanzamiento y ejecución. Por ejemplo, es imposible realizar cambios en el código en tiempo de ejecución, ya que no hay forma de propagar esos cambios a la etapa de compilación.
Cada lanzamiento debe tener siempre un ID de lanzamiento único, como una marca de tiempo del lanzamiento o un número creciente. Las entregas son un libro mayor de solo anexo y una entrega no se puede modificar una vez que se crea. Cualquier cambio debe crear una nueva versión.
Los desarrolladores de la aplicación inician las compilaciones cada vez que se implementa un nuevo código. La ejecución en tiempo de ejecución, por el contrario, puede suceder automáticamente en casos como el reinicio del servidor o el reinicio de un proceso bloqueado por el administrador de procesos.
La aplicación se ejecuta en el entorno de ejecución como uno o más procesos.
Los procesos de 12 factores son huérfanos y no comparten nada. Cualquier dato que deba persistir debe almacenarse en un servicio
de respaldo con estado, generalmente una base de datos.
El espacio de memoria o el sistema de archivos del proceso se puede utilizar como un breve caché de transacción única. Por ejemplo, descargar un archivo grande, operar en él y almacenar los resultados de la operación en la base de datos. La aplicación de 12 factores nunca asume que cualquier cosa almacenada en la memoria caché o en el disco estará disponible en una solicitud o trabajo futuro; con muchos procesos de cada tipo en ejecución, es muy probable que una solicitud futura sea atendida por un proceso diferente. Incluso cuando se ejecuta solo un proceso, un reinicio generalmente eliminará todo el estado local.
En este factor se realiza la exportación de servicios a través de la vinculación de puertos en la red.
Las aplicaciones web a veces se ejecutan dentro de un contenedor de servidor web. Por ejemplo, las aplicaciones Perl pueden ejecutarse como un módulo dentro de Apache HTTPD o las aplicaciones Java pueden ejecutarse dentro de Tomcat.
La aplicación de 12 factores es completamente autónoma y no depende de la inyección en tiempo de ejecución de un servidor web en el entorno de ejecución para crear un servicio orientado a la web. La aplicación web exporta HTTP como servicio al vincularse a un puerto y escuchar las solicitudes que ingresan en ese puerto.
Ten en cuenta también que el enfoque de enlace de puertos significa que una aplicación puede convertirse en el servicio de respaldo para otra aplicación, proporcionando la URL a la aplicación de respaldo como un identificador de recursos en la configuración de la aplicación consumidora.
Cualquier programa de computadora, una vez ejecutado, está representado por uno o más procesos. Las aplicaciones web han adoptado una variedad de formas de ejecución de procesos. Por ejemplo, los procesos Perl se ejecutan como procesos secundarios de Apache iniciados bajo demanda según sea necesario por volumen de solicitudes. Los procesos de Java adoptan el enfoque opuesto, con la JVM que proporciona un súper proceso masivo que reserva un gran bloque de recursos del sistema (CPU y memoria RAM) al inicio, con simultaneidad administrada internamente a través de subprocesos. En ambos casos, los procesos en ejecución solo son mínimamente visibles para los desarrolladores de la aplicación.
En la aplicación de 12 factores, los procesos son ciudadanos de primera clase. Los procesos en la aplicación de 12 factores toman fuertes señales del modelo de proceso Unix para ejecutar demonios de servicio. Con este modelo, el desarrollador puede diseñar su aplicación para manejar diversas cargas de trabajo asignando cada tipo de trabajo a un tipo de proceso. Por ejemplo, las solicitudes HTTP pueden ser manejadas por un proceso web y las tareas en segundo plano de larga ejecución manejadas por un proceso de trabajo.
Esto no excluye que los procesos individuales manejen su propia multiplexación interna, a través de subprocesos dentro de la máquina virtual en tiempo de ejecución, o el modelo asíncrono / evento que se encuentra en herramientas como Node.js, pero una máquina virtual individual solo puede crecer hasta cierto punto, por lo que la aplicación también debe poder abarcar varios procesos que se ejecutan en varias máquinas físicas.
Los procesos de la aplicación de 12 factores son prescindibles, lo que significa que se pueden iniciar o detener en cualquier momento. Esto facilita el escalado elástico rápido, la implementación rápida de cambios de código o configuración y la solidez de las implementaciones de producción.
Los procesos deben esforzarse por minimizar el tiempo de inicio. Idealmente, un proceso toma unos segundos desde el momento en que se ejecuta el comando de inicio hasta que el proceso está activo y listo para recibir solicitudes o trabajos. El tiempo de inicio corto proporciona más agilidad para el proceso de lanzamiento y la ampliación; y ayuda a la robustez, porque el administrador de procesos puede mover más fácilmente los procesos a nuevas máquinas físicas cuando esté justificado.
Los procesos se apagan correctamente cuando reciben una señal SIGTERM (señal de terminación) del administrador de procesos. Para un proceso web, el cierre ordenado se logra al dejar de escuchar en el puerto de servicio, permitiendo que finalice cualquier solicitud actual y luego saliendo. Implícito en este modelo es que las solicitudes HTTP son breves (no más de unos pocos segundos) o, en el caso de una consulta larga, el cliente debe intentar reconectarse sin problemas cuando se pierde la conexión.
Los procesos también deben ser robustos contra la muerte súbita, en el caso de una falla en el hardware subyacente. Si bien esto es una ocurrencia mucho menos común que un cierre ordenado con SIGTERM, aún puede suceder. Un enfoque recomendado es el uso de un backend de cola robusto, como Beanstalkd, que devuelve trabajos a la cola cuando los clientes se desconectan o agotan el tiempo de espera. De cualquier manera, una aplicación de 12 factores está diseñada para manejar terminaciones inesperadas y no agradables. El diseño solo para colisiones lleva este concepto a su conclusión lógica.
Tanto el desarrollo, la puesta en escena y la producción tienen que ser lo más similar posible; esto es para disminuir los errores en la codificación que afecten los tiempos de despliegue de las aplicaciones.
Históricamente, ha habido brechas sustanciales entre el desarrollo (un desarrollador que realiza ediciones en vivo en una implementación local de la aplicación) y la producción (una implementación en ejecución de la aplicación a la que acceden los usuarios finales). Estas brechas se manifiestan en tres áreas:
La aplicación de 12 factores está diseñada para una implementación continua al mantener pequeña la brecha entre el desarrollo y la producción. Observando las tres brechas descritas anteriormente:
Los servicios de respaldo, como la base de datos de la aplicación, el sistema de cola o la caché, es un área donde la paridad dev/prod es importante. Muchos idiomas ofrecen bibliotecas que simplifican el acceso al servicio de respaldo, incluidos adaptadores para diferentes tipos de servicios.
Las bitácoras son un historial de eventos para llevar un control más estricto, así como una supervisión en los comportamientos de las aplicaciones.
Las bitácoras proporcionan visibilidad del comportamiento de una aplicación en ejecución. En entornos basados en servidor, normalmente se escriben en un archivo en el disco (un "archivo de bitácora” o “logfile”), pero este es solo un formato de salida.
Las bitácoras son el flujo de eventos agregados ordenados por tiempo recopilados de los flujos de salida de todos los procesos en ejecución y servicios de respaldo. Las bitácoras en su forma sin procesar suelen ser un formato de texto con un evento por línea (aunque en el manejo de algunas excepciones se pueden abarcar varias líneas). Este tipo de archivos no tienen un principio ni un final fijos, sino que fluyen continuamente mientras la aplicación esté en funcionamiento.
Una aplicación de 12 factores nunca se preocupa por el enrutamiento o el almacenamiento de su flujo de salida. No debe intentar escribir o administrar archivos de bitácora. En cambio, cada proceso en ejecución escribe su flujo de eventos, sin buffer, en stdout. Durante el desarrollo local, el desarrollador verá esta transmisión en primer plano de su terminal para observar el comportamiento de la aplicación.
En las implementaciones de producción o staging, el entorno de ejecución captura la secuencia de cada proceso, la recopila junto con todas las demás secuencias de la aplicación y la enruta a uno o más destinos finales para su visualización y archivo a largo plazo. Estos destinos de archivo no son visibles ni configurables por la aplicación, sino que están completamente administrados por el entorno de ejecución.
Ejecutar tareas de administración/gestión como procesos únicos para automatizar las tareas que son repetitivas o manuales que impactan directamente a los tiempos de ejecución de las aplicaciones.
La formación del proceso es el conjunto de procesos que se utilizan para realizar el negocio habitual de la aplicación (como el manejo de solicitudes web) mientras se ejecuta. Por separado, los desarrolladores a menudo desearán realizar tareas administrativas o de mantenimiento únicas para la aplicación, como:
Los procesos de administración únicos deben ejecutarse en un entorno idéntico al de los procesos regulares de larga ejecución de la aplicación. Se ejecutan en una versión, utilizando la misma base de código y configuración que cualquier proceso que se ejecute en esa versión. El código de administración debe empaquetarse junto con el código de la aplicación para evitar problemas de sincronización.
Es importante que sepas distinguir cuándo una arquitectura de microservicios es aplicable para resolver los problemas de escalamiento que pudiera tener tu aplicación. Si tu equipo es pequeño o tu aplicación aún no alcanza un volumen de usuarios o transaccional de gran escala quizás aún haya tiempo de planear y refinar el diseño antes de migrar a una estructura que implica mayor disciplina y procesos de administración de clase mundial. Los puntos relevantes para considerar son los siguientes:
¿Cuándo será un buen momento para migrar a esta arquitectura?
¿Qué reglas de dedo / lineamientos se pueden seguir para identificar las fronteras de microservicios utilizando técnicas como DDD (domain driven design)?
¿Cómo se puede diseñar una buena manera para que los servicios colaboren entre sí, sin incrementar el acoplamiento entre ellos, usando colaboración basada en eventos?
Asegúrate de: