Este documento es el tercero de una serie de cuatro partes sobre el diseño, la compilación y la implementación de microservicios. En esta serie, se describen los diversos elementos de una arquitectura de microservicios. En ella, se incluye información de los beneficios y las desventajas del patrón de arquitectura de microservicios, y cómo aplicarlo.
- Introducción a los microservicios
- Refactorización de aplicaciones monolíticas en microservicios
- Comunicación entre servicios en una configuración de microservicios (este documento)
- Seguimiento distribuido en una aplicación de microservicios
Esta serie está dirigida a desarrolladores y arquitectos de aplicaciones que diseñan y, luego, implementan la migración para refactorizar una aplicación monolítica en una aplicación de microservicios.
En este documento, se describen las compensaciones entre la mensajería asíncrona en comparación con las API síncronas en los microservicios. En el documento, se explica la deconstrucción de una aplicación monolítica y se muestra cómo convertir una solicitud síncrona en la aplicación original en un flujo asíncrono en la nueva configuración basada en microservicios. Esta conversión incluye la implementación de transacciones distribuidas entre servicios.
Aplicación de ejemplo
En este documento, utilizarás una aplicación de comercio electrónico precompilada llamada Online Boutique. La aplicación implementa flujos de comercio electrónico básicos, como explorar elementos, agregar productos a un carrito y confirmar la compra. La aplicación también presenta recomendaciones y anuncios basados en la selección del usuario.
Separación lógica del servicio
En este documento, se aísla el servicio de pagos del resto de la aplicación. Todos los flujos en la aplicación Online Boutique original son síncronos. En la aplicación refactorizada, el proceso de pago se convierte en un flujo asíncrono. Por lo tanto, cuando recibes una solicitud de compra, en lugar de procesarla de inmediato, proporcionas una confirmación de “solicitud recibida” al usuario. En segundo plano, se activa una solicitud asíncrona en el servicio de pago para procesar el pago.
Antes de mover los datos de pago y la lógica a un servicio nuevo, debes aislar los datos y la lógica de pago de la aplicación monolítica. Cuando aíslas los datos de pago y la lógica en la aplicación monolítica, es más fácil refactorizar tu código en la misma base de código si obtienes límites de servicio de pago incorrectos (lógica o datos empresariales).
Los componentes de la aplicación monolítica de este documento ya están modularizados, por lo que están aislados unos de otros. Si tu aplicación tiene interdependencias más estrictas, debes aislar la lógica empresarial y crear clases y módulos separados. También debes separar las dependencias de bases de datos en sus propias tablas y crear clases de repositorios separadas. Cuando separas las dependencias de bases de datos, puede haber relaciones de claves externas entre las tablas divididas. Sin embargo, después de separar por completo el servicio de la aplicación monolítica, estas dependencias dejan de existir y el servicio interactúa de forma exclusiva a través de la API predefinida o los contratos de RPC.
Transacciones distribuidas y fallas parciales
Después de aislar el servicio y separarlo de la aplicación monolítica, se distribuye una transacción local en el sistema monolítico original entre varios servicios. En la implementación de la aplicación monolítica, el proceso de confirmación de la compra siguió la secuencia que se muestra en el siguiente diagrama.
Figura 1. Una secuencia de proceso de confirmación de la compra en la implementación de una aplicación monolítica.
En la figura 1, cuando la aplicación recibe una orden de compra, el controlador de confirmación de la compra llama al servicio de pago y ordena al servicio procesar el pago y guardar el pedido, respectivamente. Si algún paso falla, la transacción de la base de datos se puede revertir. Considera una situación de ejemplo en la que la solicitud de pedido se almacena de forma correcta en la tabla de pedidos, pero el pago falla. En este caso, toda la transacción se revierte y la entrada se quita de la tabla de pedidos.
Después de separar el pago en su propio servicio, el flujo de confirmación de la compra modificado es similar al siguiente diagrama:
Figura 2. Una secuencia de proceso de confirmación de la compra después de que el pago se separa en su propio servicio.
En la figura 2, la transacción ahora abarca varios servicios y sus bases de datos correspondientes, por lo que es una transacción distribuida. Cuando recibe una solicitud de pedido, el controlador de confirmación de la compra guarda los detalles del pedido en su base de datos local y llama a otros servicios para completar el pedido. Estos servicios, como el servicio de pago, pueden usar su propia base de datos local para almacenar detalles sobre el pedido.
En la aplicación monolítica, el sistema de bases de datos garantiza que las transacciones locales sean atómicas. Sin embargo, de forma predeterminada, el sistema basado en microservicios, que tiene una base de datos independiente para cada servicio, no tiene un coordinador de transacciones global que abarque las diferentes bases de datos. Debido a que las transacciones no están coordinadas de forma central, una falla en el procesamiento de un pago no revierte los cambios confirmados en el servicio de pedidos. Por lo tanto, el sistema se encuentra en un estado incoherente.
Los siguientes patrones se usan en general para administrar transacciones distribuidas:
- Protocolo de confirmación en dos fases (2PC): forma parte de una familia de protocolos de consenso. El 2PC coordina la confirmación de una transacción distribuida y mantiene la garantía de atomicidad, coherencia, aislamiento y durabilidad (ACID). El protocolo se divide en las fases de preparación y confirmación. Una transacción se confirma solo si todos los participantes votaron por ella. Si los participantes no llegan a un consenso, se revierte toda la transacción.
- Saga: el patrón Saga consiste en ejecutar transacciones locales dentro de cada microservicio que conforma la transacción distribuida. Un evento se activa al final de cada operación exitosa o con errores. Todos los microservicios involucrados en la transacción distribuida se suscriben a estos eventos. Si los siguientes microservicios reciben un evento exitoso, ejecutan su operación. Si hay una falla, los microservicios anteriores completan acciones de compensación para deshacer cambios. Saga proporciona una vista coherente del sistema, ya que garantiza que cuando se completen todos los pasos, todas las operaciones se realizarán de forma correcta o las acciones de compensación desharán todo el trabajo.
Recomendamos Saga para transacciones de larga duración. En una aplicación basada en microservicios, se esperan llamadas entre servicios y comunicación con sistemas de terceros. Por lo tanto, es mejor diseñar para lograr una coherencia eventual: reintentar los errores recuperables y exponer los eventos de compensación que con el tiempo enmiendan los errores no recuperables.
Existen varias formas de implementar un patrón Saga. Por ejemplo, puedes usar motores de tareas y flujos de trabajo, como Apache Airflow, Apache Camel o Conductor. También puedes escribir tus propios controladores de eventos a través de sistemas basados en Kafka, RabbitMQ o ActiveMQ.
La aplicación Online Boutique usa el servicio de confirmación de la compra para organizar los servicios de pago, envío y notificación por correo electrónico. El servicio de confirmación de la compra también controla el flujo de trabajo del negocio y los pedidos. Como alternativa a compilar tu propio motor de flujo de trabajo, puedes usar un componente de terceros, como Zeebe. Zeebe proporciona un modeler basado en IU. Te recomendamos evaluar con cuidado las opciones para el organizador de microservicios según los requisitos de tu aplicación. Esta elección es una parte fundamental de la ejecución y el escalamiento de tus microservicios.
Aplicación refactorizada
Para habilitar las transacciones distribuidas en la aplicación refactorizada, el servicio de confirmación de la compra controla la comunicación entre el servicio de pago, envío y correo electrónico. El flujo de trabajo del modelo y notación de procesos empresariales (BPMN) utiliza el siguiente flujo:
Figura 3. Un flujo de trabajo de pedidos que ayuda a garantizar las transacciones distribuidas en microservicios típicos.
En el diagrama anterior, se muestra el siguiente flujo de trabajo:
- El servicio de frontend recibe una solicitud de pedido y, luego, hace lo siguiente:
- Envía los artículos del pedido al servicio del carrito. Luego, el servicio del carrito guarda los detalles del pedido (Redis).
- Redirecciona a la página de confirmación de la compra. El servicio de confirmación de la compra extrae los pedidos del servicio de carrito, establece el estado del pedido como
Pending
y solicita el pago al cliente. - Confirma que el usuario pagó. Una vez que está confirmado, el servicio de confirmación de la compra le indica al servicio de correo electrónico que genere un correo electrónico de confirmación y lo envíe al cliente.
- Luego, el servicio de pago procesa la solicitud.
- Si la solicitud de pago se realiza de forma correcta, el servicio de pago actualiza el estado del pedido a
Complete
. - Si la solicitud de pago falla, el servicio de pago inicia una transacción de compensación.
- Se canceló la solicitud de pago.
- El servicio de frontend cambia el estado del pedido a
Failed
.
- Si el servicio de pago no está disponible, el tiempo de espera de la solicitud se agota después de N segundos y el servicio de confirmación de la compra inicia una transacción de compensación.
- El servicio de frontend cambia el estado del pedido a
Failed
.
- Si la solicitud de pago se realiza de forma correcta, el servicio de pago actualiza el estado del pedido a
Objetivos
- Implementar la aplicación monolítica de Online Boutique en Google Kubernetes Engine (GKE).
- Valida el flujo de confirmación de la compra de la aplicación monolítica.
- Implementa la versión de microservicios de la aplicación refactorizada de la aplicación monolítica
- Verificar que el nuevo flujo de confirmación de la compra funcione
- Verificar que las transacciones distribuidas y las acciones de compensación funcionen si hay una falla
Costos
En este documento, usarás los siguientes componentes facturables de Google Cloud:
Para generar una estimación de costos en función del uso previsto, usa la calculadora de precios.
Cuando termines esta guía, puedes borrar los recursos que creaste para evitar que se sigan facturando. Para obtener más información, consulta Realiza una limpieza.
Antes de comenzar
-
In the Google Cloud console, on the project selector page, select or create a Google Cloud project.
-
Asegúrate de que la facturación esté habilitada para tu proyecto de Google Cloud.
-
In the Google Cloud console, activate Cloud Shell.
At the bottom of the Google Cloud console, a Cloud Shell session starts and displays a command-line prompt. Cloud Shell is a shell environment with the Google Cloud CLI already installed and with values already set for your current project. It can take a few seconds for the session to initialize.
Habilita las APIs para Compute Engine, Google Kubernetes Engine, Cloud SQL, Artifact Analysis y Container Registry:
gcloud services enable \ compute.googleapis.com \ sql-component.googleapis.com \ servicenetworking.googleapis.com\ container.googleapis.com \ containeranalysis.googleapis.com \ containerregistry.googleapis.com \ sqladmin.googleapis.com
Exporta las siguientes variables de entorno:
export PROJECT=$(gcloud config get-value project) export CLUSTER=$PROJECT-gke export REGION="us-central1"
Implementa la aplicación monolítica de comercio electrónico
En esta sección, implementarás la aplicación monolítica Online Boutique en un clúster de GKE. La aplicación usa Cloud SQL como su base de datos relacional. En el siguiente diagrama, se ilustra la arquitectura de la aplicación monolítica:
Figura 4. Un cliente se conecta a la aplicación en un clúster de GKE y la aplicación se conecta a una base de datos de Cloud SQL.
Sigue estos pasos para implementar la aplicación:
Clona el repositorio de GitHub:
git clone https://github.com/GoogleCloudPlatform/monolith-to-microservices-example
Reemplaza el marcador de posición
PROJECT_ID
en el archivo de manifiesto de variables de Terraform:cd monolith-to-microservices-example/setup && \ sed -i -e "s/\[PROJECT_ID\]/$PROJECT/g" terraform.tfvars
Ejecuta las secuencias de comandos de Terraform para completar la configuración de la infraestructura y, luego, implementarla. Para obtener más información acerca de Terraform, consulta Comienza a usar Terraform en Google Cloud.
terraform init && terraform apply -auto-approve
La secuencia de comandos de Terraform crea lo siguiente:
- Una red de VPC llamada
PROJECT_ID-vpc
- Un clúster de GKE llamado
PROJECT_ID-gke
- Una instancia de Cloud SQL llamada
PROJECT_ID-mysql
- Una base de datos llamada
ecommerce
que usa la aplicación - Un usuario
root
con la contraseña configurada comopassword
- Una base de datos llamada
Puedes modificar la secuencia de comandos de Terraform para generar una contraseña de manera automática. Esta configuración usa un ejemplo simplificado que no debes usar en producción.
El aprovisionamiento de la infraestructura puede tardar hasta 10 minutos. Cuando la secuencia de comandos se realiza de forma correcta, el resultado es similar al siguiente:
... Apply complete! Resources: 8 added, 0 changed, 0 destroyed. Outputs: kubernetes_cluster_name = PROJECT_ID-gke sql_database_name = PROJECT_ID-mysql vpc_name = PROJECT_ID-vpc
- Una red de VPC llamada
Conéctate al clúster y crea un espacio de nombres llamado
monolith
. Debes implementar la aplicación en su propio espacio de nombres en el clúster de GKE.gcloud container clusters get-credentials $CLUSTER \ --region $REGION \ --project $PROJECT && \ kubectl create ns monolith
La aplicación que se ejecuta en GKE usa Kubernetes Secrets para acceder a la base de datos de Cloud SQL. Crea un secreto que use las credenciales de usuario para la base de datos:
kubectl create secret generic dbsecret \ --from-literal=username=root \ --from-literal=password=password -n monolith
Compila la imagen de monolith y súbela a Container Registry:
cd ~/monolith gcloud builds submit --tag gcr.io/$PROJECT_ID/ecomm
Actualiza la referencia en el archivo
deploy.yaml
a la imagen de Docker recién creada:cd ~/monolith sed -i -e "s/\[PROJECT_ID\]/$PROJECT/g" deploy.yaml
Reemplaza los marcadores de posición en los archivos de manifiesto de la implementación y, luego, implementa la aplicación:
cd .. && \ DB_IP=$(gcloud sql instances describe $PROJECT-mysql | grep "ipAddress:" | tail -1 | awk -F ":" '{print $NF}') sed -i -e "s/\[DB_IP\]/$DB_IP/g" monolith/deploy.yaml kubectl apply -f monolith/deploy.yaml
Verifica el estado de la implementación:
kubectl rollout status deployment/ecomm -n monolith
La salida se verá de la siguiente manera.
Waiting for deployment "ecomm" rollout to finish: 0 of 1 updated replicas are available... deployment "ecomm" successfully rolled out
Obtén la dirección IP de la aplicación implementada:
kubectl get svc ecomm -n monolith \ -o jsonpath="{.status.loadBalancer.ingress[*].ip}" -w
Espera que se publique la dirección IP del balanceador de cargas. Para salir del comando, presiona
Ctrl+C
. Anota la dirección IP del balanceador de cargas y, luego, accede a la aplicación en la URLhttp://IP_ADDRESS
. El balanceador de cargas puede tardar un tiempo en estar en buen estado y comenzar a pasar tráfico.
Valida el flujo de confirmación de la compra de la aplicación monolítica
En esta sección, crearás un pedido de prueba para validar el flujo de confirmación de la compra.
- Ve a la URL que anotaste en la sección anterior,
http://IP_ADDRESS
. - En la página principal de la aplicación que aparece, selecciona cualquier producto y, luego, haz clic en Agregar al carrito.
- Para crear una compra de prueba, haz clic en Realizar tu pedido:
- Cuando la confirmación de la compra se realiza de forma correcta, aparece la ventana de confirmación del pedido y muestra un ID de confirmación del pedido.
Para ver los detalles del pedido, conéctate a la base de datos:
gcloud sql connect $PROJECT-mysql --user=root
También puedes usar cualquier otro método admitido para conectarte a la base de datos. Cuando se te solicite, ingresa la contraseña como
password
.Para ver los detalles del pedido guardado, ejecuta el siguiente comando:
select cart_id from ecommerce.cart;
El resultado luce de la siguiente manera:
+--------------------------------------+ | cart_id | +--------------------------------------+ | 7cb9ab11-d268-477f-bf4d-4913d64c5b27 | +--------------------------------------+
Implementa la aplicación de comercio electrónico basada en microservicios
En esta sección, implementarás la aplicación refactorizada. Este documento solo se centra en la separación de los servicios de frontend y pago. En el siguiente documento de esta serie, Seguimiento distribuido en una aplicación de microservicios, se describen otros servicios, como los servicios de recomendación y anuncios, que puedes separar de la aplicación monolítica. El servicio de confirmación de la compra controla las transacciones distribuidas entre el frontend y los servicios de pago y se implementa como un servicio de Kubernetes en el clúster de GKE, como se muestra en el siguiente diagrama:
Figura 5. El servicio de confirmación de la compra organiza las transacciones entre el servicio de carrito, pago y correo electrónico.
Implementa los microservicios
En esta sección, debes usar la infraestructura que aprovisionaste antes para implementar microservicios en su propio espacio de nombres microservice
.
Asegúrate de que tienes los siguientes requerimientos:
- Proyecto de Google Cloud
- Entorno de shell con
gcloud
,git
ykubectl
En Cloud Shell, clona el repositorio de microservicios.
git clone https://github.com/GoogleCloudPlatform/microservices-demo cd microservices-demo/
Configura el proyecto y la región de Google Cloud y asegúrate de que la API de GKE esté habilitada:
export PROJECT_ID=PROJECT_ID export REGION=us-central1 gcloud services enable container.googleapis.com \ --project=${PROJECT_ID}
Sustituye
por el ID de tu proyecto de Google Cloud. Crea un clúster de GKE y obtén sus credenciales:
gcloud container clusters create-auto online-boutique \ --project=${PROJECT_ID} --region=${REGION}
La creación del clúster puede tomar unos minutos.
Implementa microservicios en el clúster:
kubectl apply -f ./release/kubernetes-manifests.yaml
Espera a que los Pods estén listos.
kubectl get pods
Después de unos minutos, verás los pods en estado
Running
.Accede al frontend web en un navegador con la dirección IP externa del frontend:
kubectl get service frontend-external | awk '{print $4}'
Visita
http://EXTERNAL_IP
en un navegador web para acceder a tu instancia de Online Boutique.
Valida el flujo de confirmación de la compra nuevo
- Para verificar el flujo del proceso de confirmación de la compra, selecciona un producto y realiza un pedido, como se describe en la sección anterior Valida el flujo de confirmación de la compra de la aplicación monolítica.
- Cuando completas la confirmación del pedido, la ventana de confirmación no muestra un ID de confirmación. En cambio, la ventana de confirmación te dirige a tu correo electrónico para ver los detalles de la confirmación.
Para verificar que se recibió el pedido, que el servicio de pagos haya procesado el pago y que se hayan actualizado los detalles del pedido, ejecuta el siguiente comando:
kubectl logs -f deploy/checkoutservice --tail=100
El resultado luce de la siguiente manera:
[...] {"message":"[PlaceOrder] user_id=\"98828e7a-b2b3-47ce-a663-c2b1019774a3\" user_currency=\"CAD\"","severity":"info","timestamp":"2023-08-10T04:19:20.498893921Z"} {"message":"payment went through (transaction_id: f0b4a592-026f-4b4a-9892-ce86d2711aed)","severity":"info","timestamp":"2023-08-10T04:19:20.528338189Z"} {"message":"order confirmation email sent to \"someone@example.com\"","severity":"info","timestamp":"2023-08-10T04:19:20.540275988Z"}
Para salir de los registros, presiona
Ctrl+C
.Verifica que el pago se realizó de forma correcta.
kubectl logs -f deploy/paymentservice -n --tail=100
El resultado luce de la siguiente manera:
[...] {"severity":"info","time":1691641282208,"pid":1,"hostname":"paymentservice-65cc7795f6-r5m8r","name":"paymentservice-charge","message":"Transaction processed: visa ending 0454 Amount: CAD119.30128260"} {"severity":"info","time":1691641300051,"pid":1,"hostname":"paymentservice-65cc7795f6-r5m8r","name":"paymentservice-server","message":"PaymentService#Charge invoked with request {\"amount\":{\"currency_code\":\"USD\",\"units\":\"137\",\"nanos\":850000000},\"credit_card\":{\"credit_card_number\":\"4432-8015-6152-0454\",\"credit_card_cvv\":672,\"credit_card_expiration_year\":2039,\"credit_card_expiration_month\":1}}"}
Para salir de los registros, presiona
Ctrl+C
.Verifica que se haya enviado el correo electrónico de confirmación del pedido:
kubectl logs -f deploy/emailservice -n --tail=100
El resultado luce de la siguiente manera:
[...] {"timestamp": 1691642217.5026057, "severity": "INFO", "name": "emailservice-server", "message": "A request to send order confirmation email to kalani@examplepetstore.com has been received."}
Los mensajes de registro de cada microservicio indican que la transacción distribuida en los servicios de confirmación de la compra, pago y correo electrónico se completaron de forma correcta.
Valida la acción de compensación en una transacción distribuida
En esta sección, se simula una situación en la que un cliente realiza un pedido y el servicio de pago falla.
Para simular la falta de disponibilidad del servicio, borra la implementación y el servicio de pago:
kubectl delete deploy paymentservice && \ kubectl delete svc paymentservice
Accede a la aplicación de nuevo y completa el flujo de confirmación de la compra. En este ejemplo, si el servicio de pago no responde, se agota el tiempo de espera de la solicitud y se activa una acción de compensación.
En el frontend de la IU, haz clic en el botón Realizar pedido. El resultado se ve de la manera siguiente:
HTTP Status: 500 Internal Server Error rpc error: code = Internal desc = failed to charge card: could not charge the card: rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing: dial tcp: lookup paymentservice on 34.118.224.10:53: no such host" failed to complete the order main.(*frontendServer).placeOrderHandler /src/handlers.go:360
Revisa los registros del servicio de frontend:
kubectl logs -f deploy/frontend --tail=100
El resultado se ve de la manera siguiente:
[...] {"error":"failed to complete the order: rpc error: code = Internal desc = failed to charge card: could not charge the card: rpc error: code = Unavailable desc = connection error: desc = \"transport: Error while dialing: dial tcp: lookup paymentservice on 34.118.224.10:53: no such host\"","http.req.id":"0a4cb058-ee9b-470a-9bb1-3a965636022e","http.req.method":"POST","http.req.path":"/cart/checkout","message":"request error","session":"96c94881-a435-4490-9801-c788dc400cc1","severity":"error","timestamp":"2023-08-11T18:25:47.127294259Z"}
Revisa los registros del servicio de confirmación de la compra:
kubectl logs -f deploy/frontend --tail=100
El resultado se ve de la manera siguiente:
[...] {"message":"[PlaceOrder] user_id=\"96c94881-a435-4490-9801-c788dc400cc1\" user_currency=\"USD\"","severity":"info","timestamp":"2023-08-11T18:25:46.947901041Z"} {"message":"[PlaceOrder] user_id=\"96c94881-a435-4490-9801-c788dc400cc1\" user_currency=\"USD\"","severity":"info","timestamp":"2023-08-11T19:54:21.796343643Z"}
Ten en cuenta que no hay ninguna llamada posterior al servicio de correo electrónico para enviar una notificación. No hay ningún registro de transacciones, como
payment went through (transaction_id: 06f0083f-fa47-4d91-8258-6d61edfab1ca)
Revisa los registros del servicio de correo electrónico:
kubectl logs -f deploy/emailservice --tail=100
Ten en cuenta que no hay entradas de registro creadas para la transacción con errores en el servicio de correo electrónico.
Como organizador, si una llamada de servicio falla, el servicio de confirmación de la compra muestra un estado de error y sale del proceso.
Limpia
Para evitar que se apliquen cargos a tu cuenta de Google Cloud por los recursos usados en este instructivo, borra el proyecto que contiene los recursos o conserva el proyecto y borra los recursos individuales.
Si planificas completar los pasos del siguiente documento de esta serie, Seguimiento distribuido en una aplicación de microservicios, puedes volver a usar el proyecto y los recursos en lugar de borrarlos.
Borra el proyecto
- En la consola de Google Cloud, ve a la página Administrar recursos.
- En la lista de proyectos, elige el proyecto que quieres borrar y haz clic en Borrar.
- En el diálogo, escribe el ID del proyecto y, luego, haz clic en Cerrar para borrar el proyecto.
Borra recursos
Si deseas conservar el proyecto de Google Cloud que usaste en este instructivo, borra los recursos individuales.
En Cloud Shell, ejecuta el siguiente comando:
cd setup && terraform destroy -auto-approve
Para borrar el clúster de microservicios con Google Cloud CLI, ejecuta el siguiente comando:
gcloud container clusters delete online-boutique \ --location $REGION
Próximos pasos
- Obtén más información de la arquitectura de microservicios.
- Lee el primer documento de esta serie para obtener información de los microservicios, sus beneficios, sus desafíos y sus casos de uso.
- Lee el segundo documento de esta serie para obtener información de las estrategias de refactorización de aplicaciones para descomponer microservicios.
- Lee el último documento de esta serie para obtener información del seguimiento distribuido de solicitudes entre microservicios.