ID de región
El REGION_ID
es un código abreviado que Google asigna en función de la región que selecciones al crear tu aplicación. El código no corresponde a un país o provincia, aunque algunos IDs de región pueden parecerse a los códigos de país y provincia que se usan habitualmente. En las aplicaciones creadas después de febrero del 2020, REGION_ID.r
se incluye en las URLs de App Engine. En las aplicaciones creadas antes de esa fecha, el ID de región es opcional en la URL.
Los microservicios de App Engine suelen llamarse entre sí mediante APIs RESTful basadas en HTTP. También es posible invocar microservicios en segundo plano mediante colas de tareas. Se aplican los principios de diseño de APIs que se describen en este artículo. Es importante seguir ciertos patrones para asegurarse de que la aplicación basada en microservicios sea estable, segura y tenga un buen rendimiento.
Usar contratos sólidos
Uno de los aspectos más importantes de las aplicaciones basadas en microservicios es la capacidad de desplegar microservicios de forma completamente independiente entre sí. Para conseguir esta independencia, cada microservicio debe proporcionar un contrato con versiones y bien definido a sus clientes, que son otros microservicios. Cada servicio no debe romper estos contratos versionados hasta que se sepa que ningún otro microservicio depende de un contrato versionado concreto. Ten en cuenta que es posible que otros microservicios tengan que volver a una versión de código anterior que requiera un contrato anterior, por lo que es importante tener en cuenta este hecho en tus políticas de retirada y de obsolescencia.
Una cultura basada en contratos sólidos y versionados es probablemente el aspecto organizativo más difícil de una aplicación estable basada en microservicios. Los equipos de desarrollo deben comprender la diferencia entre un cambio incompatible y un cambio compatible. Deben saber cuándo se requiere una nueva versión principal. Deben saber cómo y cuándo se puede retirar un contrato antiguo. Los equipos deben emplear técnicas de comunicación adecuadas, como avisos de obsolescencia y de desactivación, para que los usuarios estén al tanto de los cambios en los contratos de microservicios. Aunque pueda parecer complicado, si incorporas estas prácticas a tu cultura de desarrollo, con el tiempo conseguirás grandes mejoras en la velocidad y la calidad.
Direcciones de microservicios
Se pueden abordar directamente los servicios y las versiones de código. De esta forma, puedes implementar nuevas versiones de código junto con las versiones de código que ya tengas y probar el nuevo código antes de convertirlo en la versión de servicio predeterminada.
Cada proyecto de App Engine tiene un servicio predeterminado y cada servicio tiene una versión de código predeterminada. Para dirigirte al servicio predeterminado de la versión predeterminada de un proyecto, usa la siguiente URL:
https://PROJECT_ID.REGION_ID.r.appspot.com
Si despliegas un servicio llamado user-service
, puedes acceder a la versión de servicio predeterminada de ese servicio mediante la siguiente URL:
https://user-service-dot-my-app.REGION_ID.r.appspot.com
Si implementa una segunda versión del código no predeterminada llamada banana
en el servicio user-service
, puede acceder directamente a esa versión del código
con la siguiente URL:
https://banana-dot-user-service-dot-my-app.REGION_ID.r.appspot.com
Ten en cuenta que, si implementas una segunda versión del código, que no sea la predeterminada, llamada cherry
en el servicio default
, puedes acceder a esa versión del código mediante la siguiente URL:
https://cherry-dot-my-app.REGION_ID.r.appspot.com
App Engine aplica la regla de que los nombres de las versiones de código del servicio predeterminado no pueden coincidir con los nombres de los servicios.
La dirección directa de versiones de código específicas solo se debe usar para pruebas de humo y para facilitar las pruebas A/B, la implementación y la reversión. En su lugar, el código de cliente solo debe hacer referencia a la versión de servicio predeterminada o a una versión de servicio específica:
https://PROJECT_ID.REGION_ID.r.appspot.com
https://SERVICE_ID-dot-PROJECT_ID.REGION_ID.r.appspot.com
Este estilo de direccionamiento permite que los microservicios implementen nuevas versiones de sus servicios, incluidas las correcciones de errores, sin necesidad de realizar ningún cambio en los clientes.
Usar versiones de la API
Cada API de microservicio debe tener una versión principal de la API en la URL, como la siguiente:
/user-service/v1/
Esta versión principal de la API identifica claramente en los registros qué versión de la API del microservicio se está llamando. Lo que es más importante, la versión principal de la API genera URLs diferentes, por lo que las nuevas versiones principales de la API se pueden usar junto con las versiones principales antiguas:
/user-service/v1/ /user-service/v2/
No es necesario incluir la versión secundaria de la API en la URL, ya que, por definición, las versiones secundarias de la API no introducirán ningún cambio incompatible. De hecho, si se incluyera la versión secundaria de la API en la URL, se produciría una proliferación de URLs y se generaría incertidumbre sobre la capacidad de un cliente para cambiar a una nueva versión secundaria de la API.
Ten en cuenta que en este artículo se presupone un entorno de integración y entrega continuas en el que la rama principal se implementa siempre en App Engine. En este artículo se distinguen dos conceptos de versión:
Versión del código: se corresponde directamente con una versión de servicio de App Engine y representa una etiqueta de confirmación concreta de la rama principal.
Versión de la API: se asigna directamente a una URL de la API y representa la forma de los argumentos de la solicitud, la forma del documento de respuesta y el comportamiento de la API.
En este artículo también se da por hecho que una sola implementación de código implementará tanto las versiones antiguas como las nuevas de una API en una versión de código común. Por ejemplo, tu rama principal desplegada puede implementar /user-service/v1/
y /user-service/v2/
. Cuando se lanzan nuevas versiones secundarias y de parche, este enfoque te permite dividir el tráfico entre dos versiones de código independientemente de las versiones de la API que implemente el código.
Tu organización puede desarrollar tu /user-service/v1/
y tu /user-service/v2/
en ramas de código diferentes. Es decir, ninguna implementación de código implementará ambos al mismo tiempo. Este modelo también se puede usar en App Engine, pero para dividir el tráfico, tendrías que mover la versión principal de la API al nombre del servicio.
Por ejemplo, tus clientes usarían las siguientes URLs:
http://user-service-v1.my-app.REGION_ID.r.appspot.com/user-service/v1/ http://user-service-v2.my-app.REGION_IDappspot.com/user-service/v2/
La versión principal de la API se incluye en el nombre del servicio, como user-service-v1
y user-service-v2
.
(Las partes /v1/
y /v2/
de la ruta son redundantes en este modelo y se podrían quitar, aunque pueden ser útiles en el análisis de registros).
Este modelo requiere un poco más de trabajo, ya que probablemente necesites actualizar tus secuencias de comandos de implementación para implementar nuevos servicios en los cambios importantes de la versión de la API. Además, ten en cuenta el número máximo de servicios permitidos por aplicación de App Engine.
Cambios con puntos de ruptura y sin ellos
Es importante entender la diferencia entre un cambio incompatible y un cambio compatible. Los cambios incompatibles suelen ser sustractivos, lo que significa que eliminan alguna parte del documento de solicitud o respuesta. Si cambia la forma del documento o el nombre de las claves, se pueden producir cambios incompatibles. Los nuevos argumentos obligatorios siempre son cambios incompatibles. También se pueden producir cambios incompatibles si cambia el comportamiento del microservicio.
Los cambios no incompatibles suelen ser aditivos. Un nuevo argumento de solicitud opcional o una nueva sección adicional en el documento de respuesta son cambios no disruptivos. Para conseguir cambios no disruptivos, es fundamental elegir la serialización en la red. Muchas serializaciones son compatibles con los cambios no significativos: JSON, Protocol Buffers o Thrift. Cuando se deserializan, estas serializaciones ignoran de forma silenciosa la información adicional e inesperada. En los idiomas dinámicos, la información adicional simplemente aparece en el objeto deserializado.
Considera la siguiente definición JSON del servicio /user-service/v1/
:
{
"userId": "UID-123",
"firstName": "Jake",
"lastName": "Cole",
"username": "jcole@example.com"
}
El siguiente cambio incompatible requeriría cambiar la versión del servicio a
/user-service/v2/
:
{
"userId": "UID-123",
"name": "Jake Cole", # combined fields
"email": "jcole@example.com" # key change
}
Sin embargo, el siguiente cambio no disruptivo no requiere una nueva versión:
{
"userId": "UID-123",
"firstName": "Jake",
"lastName": "Cole",
"username": "jcole@example.com",
"company": "Acme Corp." # new key
}
Desplegar nuevas versiones secundarias de la API sin cambios incompatibles
Al desplegar una nueva versión secundaria de la API, App Engine permite que la nueva versión del código se publique junto con la antigua. En App Engine, aunque puedes dirigirte directamente a cualquiera de las versiones desplegadas, solo una de ellas es la versión de servicio predeterminada. Recuerda que hay una versión de servicio predeterminada para cada servicio. En este ejemplo, tenemos la versión antigua del código, llamada apple
, que es la versión de servicio predeterminada, y desplegamos la nueva versión del código como una versión paralela, llamada banana
. Ten en cuenta que las URLs de los microservicios de ambos son las mismas /user-service/v1/
, ya que vamos a implementar un cambio menor de la API que no va a provocar errores.
App Engine proporciona mecanismos para migrar automáticamente el tráfico de apple
a banana
marcando la nueva versión de código banana
como la versión de servicio predeterminada. Cuando se defina la nueva versión de servicio predeterminada, no se dirigirá ninguna solicitud nueva a apple
y todas las solicitudes nuevas se dirigirán a banana
. De esta forma, puedes cambiar a una nueva versión de código que implemente una nueva versión menor o de parche de la API sin que afecte a los microservicios del cliente.
En caso de error, la restauración se consigue invirtiendo el proceso anterior: vuelve a definir la versión de servicio predeterminada como la antigua, apple
en nuestro ejemplo. Todas las solicitudes nuevas se redirigirán a la versión antigua del código y no se redirigirán solicitudes nuevas a banana
. Ten en cuenta que las solicitudes en curso se pueden completar.
App Engine también ofrece la posibilidad de dirigir solo un determinado porcentaje del tráfico a la nueva versión del código. Este proceso se suele denominar lanzamiento canary y el mecanismo se llama división del tráfico en App Engine. Puedes dirigir el 1%, el 10%, el 50 % o el porcentaje de tráfico que quieras a las nuevas versiones de tu código, y puedes ajustar esta cantidad con el tiempo. Por ejemplo, puedes implementar la nueva versión del código en un plazo de 15 minutos, aumentando lentamente el tráfico y comprobando si hay algún problema que indique que es necesario revertir el cambio. Este mismo mecanismo te permite hacer pruebas A/B con dos versiones de código: define la división del tráfico en un 50% y compara las características de rendimiento y la tasa de errores de las dos versiones de código para confirmar las mejoras esperadas.
En la siguiente imagen se muestran los ajustes de división del tráfico en la consolaGoogle Cloud :
Desplegar nuevas versiones principales de la API con cambios incompatibles
Cuando implementas versiones principales de la API que provocan cambios, el proceso de actualización y restauración es el mismo que para las versiones secundarias de la API que no provocan cambios. Sin embargo, normalmente no se realiza ninguna división del tráfico ni ninguna prueba A/B, ya que la versión de la API que provoca un cambio radical es una URL recién publicada, como /user-service/v2/
. Por supuesto, si has cambiado la implementación subyacente de tu versión principal antigua de la API, puede que quieras seguir usando la división del tráfico para comprobar que tu versión principal antigua de la API sigue funcionando como esperabas.
Cuando se implementa una nueva versión principal de la API, es importante recordar que las versiones principales antiguas de la API también pueden seguir funcionando. Por ejemplo, /user-service/v1/
podría seguir ofreciéndose cuando se lance /user-service/v2/
. Este hecho es una parte esencial de las versiones de código independientes. Solo puedes rechazar versiones principales antiguas de la API después de verificar que ningún otro microservicio las necesita, incluidos otros microservicios que puedan necesitar volver a una versión de código anterior.
Por ejemplo, supongamos que tienes un microservicio llamado web-app
que depende de otro microservicio llamado user-service
. Imagina que user-service
necesita cambiar alguna implementación subyacente que hará que sea imposible admitir la versión principal antigua de la API que usa web-app
, como combinar firstName
y lastName
en un solo campo llamado name
. Es decir,
user-service
tiene que desactivar una versión principal antigua de la API.
Para llevar a cabo este cambio, se deben realizar tres implementaciones independientes:
En primer lugar,
user-service
debe implementar/user-service/v2/
y seguir ofreciendo asistencia para/user-service/v1/
. Esta implementación puede requerir que se escriba código temporal para admitir la retrocompatibilidad, lo que es una consecuencia habitual en las aplicaciones basadas en microservicios.A continuación,
web-app
debe implementar un código actualizado que cambie su dependencia de/user-service/v1/
a/user-service/v2/
.Por último, después de que el equipo de
user-service
haya verificado queweb-app
ya no requiere/user-service/v1/
y queweb-app
no necesita revertir los cambios, el equipo puede implementar el código que elimina el antiguo endpoint/user-service/v1/
y cualquier código temporal que haya necesitado para admitirlo.
Aunque toda esta actividad pueda parecer pesada, es un proceso esencial en las aplicaciones basadas en microservicios y es precisamente el proceso que permite ciclos de lanzamiento de desarrollo independientes. Para ser claros, este proceso parece ser bastante dependiente, pero es importante destacar que cada paso anterior puede producirse en plazos independientes, y que el avance y el retroceso se producen en el ámbito de un único microservicio. Solo se fija el orden de los pasos, que pueden llevarse a cabo a lo largo de muchas horas, días o incluso semanas.
Siguientes pasos
- Consulta una descripción general de la arquitectura de microservicios en App Engine.
- Consulta cómo crear y asignar nombres a entornos de desarrollo, prueba, control de calidad, preproducción y producción con microservicios en App Engine.
- Consulta las prácticas recomendadas para mejorar el rendimiento de los microservicios.
- Consulta cómo migrar una aplicación monolítica a una con microservicios.