Container Apps y AKS resuelven cómo ejecutar servicios que están siempre disponibles. Pero buena parte de lo que hace Contoso Airlines no es así. Generar el PDF de una tarjeta de embarque ocupa dos segundos y ocurre cuando un pasajero confirma su reserva. Sincronizar el catálogo de tarifas ocurre cuando cambia un documento en cosmos-contoso-tarifas-pro. Entre un evento y el siguiente no hay nada que hacer, y sin embargo hoy Contoso paga por un contenedor encendido esperando.
Azure Functions es la respuesta: escribes una función, declaras qué la dispara, y Azure se encarga de ejecutarla cuando toca y de que no exista el resto del tiempo. Esta lección va más allá del «hola mundo»: el modelo de desencadenadores y enlaces, los planes de hospedaje con sus contrapartidas reales, las dos funciones de producción de Contoso, la orquestación con Durable Functions y la lección más dura del modelo, que es la idempotencia.
Contenido
- Qué es la computación sin servidor y qué se paga
- Desencadenadores y enlaces: el modelo mental
- Planes de hospedaje comparados
- Crear el proyecto y ejecutarlo en local
GenerarTarjetaEmbarque: la función de la colaSincronizarTarifas: la fuente de cambios de Cosmos DB- Configuración, identidad administrada y Key Vault
- Durable Functions y el flujo de check-in
- Idempotencia, reintentos, tiempos de espera y límites
- Pruebas, despliegue y supervisión
- Cuándo NO usar funciones
- Errores Comunes y Consejos
- Ejercicios
- Conclusión
- Qué es la computación sin servidor y qué se paga
«Sin servidor» no significa que no haya servidores; significa que no son asunto tuyo. No eliges tamaño de máquina, no parcheas, no dimensionas: escribes una unidad de código y la plataforma la ejecuta y la escala.
Lo que se paga en el plan de Consumo son tres cosas: el número de ejecuciones, el consumo en gigabytes-segundo (memoria asignada multiplicada por el tiempo de ejecución) y el tráfico saliente. Hay una asignación gratuita mensual generosa, y dos consecuencias prácticas. La primera: una función que no se ejecuta no cuesta nada, y ahí está el ahorro frente al contenedor encendido. La segunda, menos intuitiva: el coste es proporcional al tiempo de ejecución, así que una función lenta cuesta más, y una función que espera bloqueada a una llamada HTTP externa te cobra por esperar.
- Desencadenadores y enlaces: el modelo mental
Aquí está el concepto que hay que interiorizar. Una función tiene exactamente un desencadenador —lo que decide cuándo se ejecuta— y cero o más enlaces de entrada y de salida, que son conexiones declarativas a otros servicios. La plataforma se encarga de la conexión, la autenticación, la deserialización y los reintentos; tú recibes objetos y devuelves objetos.
| Desencadenador | Se dispara cuando | Uso en Contoso |
|---|---|---|
| HTTP | Llega una petición | API ligera, webhooks de la pasarela |
| Temporizador | Se cumple una expresión cron | Purga nocturna de reservas caducadas |
| Blob | Se crea o modifica un blob | Procesar un fichero cargado |
| Cola de Storage | Aparece un mensaje | cola-emision-tarjetas |
| Service Bus | Llega un mensaje a cola o suscripción | Eventos de reserva confirmada (06-05) |
| Event Grid | Se publica un evento | Blob nuevo en sttarjetascontosopro |
| Cosmos DB | Cambia un documento (fuente de cambios) | Catálogo de tarifas |
La diferencia que hace el modelo valioso son los enlaces. Compara:
// SIN enlaces: fontaneria que hay que escribir, probar y mantener
var credencial = new DefaultAzureCredential();
var cliente = new BlobServiceClient(
new Uri("https://sttarjetascontosopro.blob.core.windows.net"), credencial);
var contenedor = cliente.GetBlobContainerClient("tarjetas-embarque");
await contenedor.CreateIfNotExistsAsync();
await contenedor.GetBlobClient(nombre).UploadAsync(flujo, overwrite: true);
// CON enlace de salida: se declara, y ya esta
[BlobOutput("tarjetas-embarque/{nombre}.pdf", Connection = "AlmacenTarjetas")]
public byte[] Pdf { get; set; }Seis líneas de fontanería sustituidas por una declaración. Y no es solo brevedad: la conexión, los reintentos y la autenticación con identidad administrada los gestiona el tiempo de ejecución.
- Planes de hospedaje comparados
| Consumo | Flex Consumo | Premium | App Service | |
|---|---|---|---|---|
| Facturación | Por ejecución y GB-s | Por ejecución y GB-s | Instancias siempre activas | Plan de App Service |
| Arranque en frío | Sí, notable | Reducido; instancias siempre listas | No | No |
| Escalado máximo | 200 instancias | Alto, configurable | 100 instancias | Manual o automático |
| Integración con red virtual | No | Sí | Sí | Sí |
| Duración máxima | 5 min (hasta 10) | Configurable | Ilimitada | Ilimitada |
| Coste sin tráfico | Cero | Casi cero | Alto (instancia mínima) | El del plan |
Lo que elige Contoso y por qué:
GenerarTarjetaEmbarqueva en Flex Consumo: necesita red virtual para llegar ape-storage-tarjetasype-sql-reservaspor punto privado, cosa que el Consumo clásico no permite, y su carga es a ráfagas, con casi cero consumo de madrugada.SincronizarTarifasva también en Flex Consumo y en la misma aplicación: comparte red y configuración, y su volumen es bajo.- La orquestación del check-in con Durable Functions va en Premium. Sus orquestaciones duran horas —esperan a que el pasajero suba un documento—, y ahí el arranque en frío y el límite de duración importan.
- Nada va en el plan de App Service, salvo que quisieran reutilizar
plan-contoso-api-propara aprovechar capacidad ya pagada.
- Crear el proyecto y ejecutarlo en local
npm install -g azure-functions-core-tools@4 --unsafe-perm true # el host, en tu maquina
func init ContosoFunciones --worker-runtime dotnet-isolated
cd ContosoFunciones
func new --name GenerarTarjetaEmbarque --template "Queue trigger"
func start # ejecuta las funciones en local, con depuracionEl modelo aislado (dotnet-isolated) es el vigente: la función corre en su propio proceso, desacoplada de la versión del host. Los valores de configuración locales van en local.settings.json, que nunca se confirma al repositorio —está en .gitignore por una razón—; en Azure, esos mismos nombres se leen de la configuración de la aplicación.
GenerarTarjetaEmbarque: la función de la cola
GenerarTarjetaEmbarque: la función de la colaCuando el pasajero completa el check-in, app-contoso-reservas-pro encola un mensaje en cola-emision-tarjetas de stoperacionescontosopro. Esta función lo consume, genera el PDF y lo deja en el contenedor tarjetas-embarque de sttarjetascontosopro.
public record SolicitudTarjeta(string Localizador, string NumeroVuelo,
string Pasajero, string Asiento, DateTime Salida);
public class GenerarTarjetaEmbarque
{
private readonly ILogger<GenerarTarjetaEmbarque> _reg;
private readonly IGeneradorPdf _pdf;
public GenerarTarjetaEmbarque(ILogger<GenerarTarjetaEmbarque> r, IGeneradorPdf p)
=> (_reg, _pdf) = (r, p);
[Function(nameof(GenerarTarjetaEmbarque))]
[BlobOutput("tarjetas-embarque/{Localizador}-{Asiento}.pdf", Connection = "AlmacenTarjetas")]
public async Task<byte[]> Run(
[QueueTrigger("cola-emision-tarjetas", Connection = "AlmacenOperaciones")]
SolicitudTarjeta solicitud,
FunctionContext contexto)
{
_reg.LogInformation("Emitiendo tarjeta {Loc} vuelo {Vuelo}",
solicitud.Localizador, solicitud.NumeroVuelo);
// Idempotencia: si el blob ya existe, este mensaje ya se proceso (apartado 9)
if (await _pdf.YaExisteAsync(solicitud.Localizador, solicitud.Asiento))
{
_reg.LogInformation("Tarjeta {Loc} ya emitida; se omite", solicitud.Localizador);
return null; // devolver null NO escribe el blob
}
return await _pdf.GenerarAsync(solicitud);
}
}Lo que hay que entender de este código:
[QueueTrigger]declara el desencadenador. El tiempo de ejecución sondea la cola, deserializa el JSON del mensaje directamente aSolicitudTarjetay te lo entrega tipado. Si la deserialización falla, el mensaje va a la cola de mensajes fallidos sin ejecutar tu código.Connection = "AlmacenOperaciones"no es una cadena de conexión: es el prefijo de un ajuste de configuración. ConAlmacenOperaciones__queueServiceUriapuntando a la cuenta y la identidad administrada asignada, no hay ninguna clave. Este es el punto que más se hace mal.[BlobOutput]con la plantilla{Localizador}-{Asiento}.pdf: los campos del mensaje entrante se interpolan en la ruta del blob. El nombre del fichero es determinista, y de ahí sale gratis la comprobación de idempotencia.- Devolver
nullno escribe el blob, la forma limpia de cortar sin lanzar una excepción. Y la inyección de dependencias funciona como en cualquier aplicación .NET: el constructor recibe el registrador y el generador de PDF, lo que hace la función probable con pruebas unitarias.
SincronizarTarifas: la fuente de cambios de Cosmos DB
SincronizarTarifas: la fuente de cambios de Cosmos DBCuando el equipo comercial actualiza una tarifa en el contenedor tarifas de cosmos-contoso-tarifas-pro, la fuente de cambios que ya conoces de 03-03 entrega el documento modificado a esta función, que refresca el índice de búsqueda y avisa a la caché del motor de disponibilidad.
public class SincronizarTarifas
{
private readonly ILogger<SincronizarTarifas> _reg;
private readonly IIndiceTarifas _indice;
public SincronizarTarifas(ILogger<SincronizarTarifas> r, IIndiceTarifas i)
=> (_reg, _indice) = (r, i);
[Function(nameof(SincronizarTarifas))]
public async Task Run(
[CosmosDBTrigger(
databaseName: "catalogo",
containerName: "tarifas",
Connection = "CosmosTarifas",
LeaseContainerName = "arrendamientos",
CreateLeaseContainerIfNotExists = true)]
IReadOnlyList<Tarifa> cambios)
{
foreach (var tarifa in cambios)
{
// Upsert por clave: procesar dos veces deja el mismo resultado
await _indice.UpsertAsync(tarifa);
_reg.LogInformation("Tarifa {Id} de {Ruta} sincronizada",
tarifa.Id, tarifa.OrigenDestino);
}
}
}El contenedor de arrendamientos merece atención: es donde el desencadenador guarda hasta dónde ha leído cada partición. Vive en la misma base de datos, consume sus propias RU/s y, si lo borras, la función reprocesa desde el principio. La función recibe lotes de cambios, no documentos sueltos, y la clave de partición /origenDestino determina el paralelismo: cada partición se procesa en orden y de forma independiente.
- Configuración, identidad administrada y Key Vault
# La aplicacion de funciones, en Flex Consumo e integrada en la red virtual
az functionapp create \
--resource-group rg-contoso-reservas-pro --name func-contoso-tarjetas-pro \
--storage-account stoperacionescontosopro --flexconsumption-location westeurope \
--runtime dotnet-isolated --runtime-version 8.0 --vnet vnet-contoso-pro --subnet snet-integracion-app \
--tags entorno=produccion proyecto=contoso-reservas \
centro-coste=CC-1042 [email protected]
# Identidad administrada asignada por el usuario, la de siempre
az functionapp identity assign -g rg-contoso-reservas-pro -n func-contoso-tarjetas-pro \
--identities $(az identity show -g rg-contoso-seguridad-pro \
-n id-contoso-api-pro --query id -o tsv)
# Conexiones POR IDENTIDAD: nombre de servicio, no clave
az functionapp config appsettings set -g rg-contoso-reservas-pro \
-n func-contoso-tarjetas-pro --settings \
"AlmacenOperaciones__queueServiceUri=https://stoperacionescontosopro.queue.core.windows.net" \
"AlmacenTarjetas__blobServiceUri=https://sttarjetascontosopro.blob.core.windows.net" \
"[email protected](SecretUri=https://kv-contoso-pro.vault.azure.net/secrets/clave-pasarela/)"Tres ideas: las conexiones con el sufijo __queueServiceUri / __blobServiceUri activan el modo por identidad, sin claves; la referencia a Key Vault con la sintaxis @Microsoft.KeyVault(...) de 04-03 resuelve el secreto en tiempo de ejecución y el valor nunca se ve en el portal; y la identidad necesita sus roles —Colaborador de datos de la cola de Storage, Colaborador de datos de Blob Storage y Usuario de secretos de Key Vault—.
- Durable Functions y el flujo de check-in
Las funciones normales son sin estado y de corta duración. ¿Y si el proceso tiene varios pasos, tarda horas y hay que saber por dónde iba? Durable Functions añade estado duradero y reanudación: un orquestador describe el flujo con código normal, y su ejecución se persiste automáticamente en cada punto de espera.
sequenceDiagram
participant P as Pasajero
participant O as OrquestadorCheckIn
participant A as Actividades
P->>O: Inicia check-in (localizador)
O->>A: ValidarReserva
A-->>O: Reserva valida
par Fan-out
O->>A: ComprobarDocumentacion
O->>A: AsignarAsiento
O->>A: CalcularEquipaje
end
A-->>O: Fan-in: resultados
O->>P: Solicita foto del pasaporte
Note over O: Espera evento externo (hasta 24 h)
P-->>O: DocumentoSubido
O->>A: EmitirTarjeta -> cola-emision-tarjetas
Los tres patrones, aplicados:
- Encadenamiento de funciones:
ValidarReserva→EmitirTarjeta. Cada paso usa la salida del anterior; si el proceso se cae en el tercer paso, al reanudar no repite los dos primeros. - Fan-out / fan-in: documentación, asiento y equipaje son independientes; se lanzan en paralelo y se espera a todas. Reduce el tiempo total al del paso más lento.
- Esperar a un evento externo: el orquestador se detiene en
WaitForExternalEvent("DocumentoSubido")con un tiempo de espera de 24 horas. Mientras espera no consume cómputo ni factura; es lo que hace inviable resolver esto con una función normal, limitada a minutos.
[Function(nameof(OrquestadorCheckIn))]
public static async Task<ResultadoCheckIn> Run(
[OrchestrationTrigger] TaskOrchestrationContext contexto)
{
var loc = contexto.GetInput<string>();
var reserva = await contexto.CallActivityAsync<Reserva>("ValidarReserva", loc);
// Fan-out: tres actividades en paralelo
await Task.WhenAll( // ...y fan-in al esperarlas
contexto.CallActivityAsync<object>("ComprobarDocumentacion", reserva),
contexto.CallActivityAsync<object>("AsignarAsiento", reserva),
contexto.CallActivityAsync<object>("CalcularEquipaje", reserva));
// Espera humana: no consume computo mientras tanto
using var cts = new CancellationTokenSource();
var evento = contexto.WaitForExternalEvent<string>("DocumentoSubido");
var plazo = contexto.CreateTimer(contexto.CurrentUtcDateTime.AddHours(24), cts.Token);
if (evento == await Task.WhenAny(evento, plazo)) cts.Cancel();
else return new ResultadoCheckIn(loc, "Caducado");
await contexto.CallActivityAsync("EmitirTarjeta", reserva);
return new ResultadoCheckIn(loc, "Completado");
}Regla de oro del orquestador: su código debe ser determinista. Nada de DateTime.Now, Guid.NewGuid() ni llamadas de E/S directas, porque el orquestador se reejecuta desde el principio cada vez que reanuda, reproduciendo su historial. Usa contexto.CurrentUtcDateTime y delega todo lo no determinista en actividades.
- Idempotencia, reintentos, tiempos de espera y límites
Esta es la lección que casi nadie cuenta al principio y que todo el mundo aprende en producción: las colas garantizan la entrega al menos una vez, no exactamente una vez. Si la función se cae después de generar el PDF pero antes de confirmar el mensaje, el mensaje reaparece y la función se ejecuta otra vez con los mismos datos. Sin protección, el pasajero recibe dos tarjetas y el registro contable queda descuadrado.
Cómo se protege, en orden de preferencia:
| Estrategia | Cómo | Aplicación en Contoso |
|---|---|---|
| Operación naturalmente idempotente | Escribir con clave determinista, upsert en vez de insert |
SincronizarTarifas: el upsert por identificador |
| Comprobación previa | Ver si el resultado ya existe antes de trabajar | GenerarTarjetaEmbarque: el blob {Localizador}-{Asiento}.pdf |
| Registro de procesados | Tabla con el identificador del mensaje | registroembarques de stoperacionescontosopro |
Sobre los reintentos: la cola reintenta automáticamente (cinco veces por defecto) y luego envía el mensaje a la cola de mensajes fallidos. Un mensaje que falla siempre —un JSON malformado, un vuelo inexistente— se llama mensaje envenenado, y sin cola de mensajes fallidos bloquearía la cola indefinidamente. Vigila esa cola con una alerta: si crece, algo está roto.
Sobre tiempos de espera y límites: 5 minutos por defecto en Consumo (ampliable a 10), configurable en Flex y sin límite en Premium, con el valor en functionTimeout de host.json. Un mensaje de cola grande no cabe (64 KB), así que manda referencias, no cargas: el identificador del blob, no el PDF. Y maxConcurrentCalls limita el paralelismo, que hay que bajar cuando la función escribe en db-reservas, porque mil instancias abriendo conexiones agotan el grupo de la base de datos.
- Pruebas, despliegue y supervisión
La lógica va en clases inyectadas —IGeneradorPdf, IIndiceTarifas—, así que se prueba con pruebas unitarias normales; la función se limita a orquestar. Para la integración, func start con el emulador de Storage. El despliegue reutiliza el pipeline de 05-04, con ranuras: se despliega en preproduccion, se calienta y se intercambia.
- task: AzureFunctionApp@2
inputs:
azureSubscription: sc-contoso-pro
appType: functionApp
appName: func-contoso-tarjetas-pro
deployToSlotOrASE: true
slotName: preproduccion
package: $(Pipeline.Workspace)/artefacto/funciones.zip
- task: AzureAppServiceManage@0 # y despues el intercambio
inputs:
azureSubscription: sc-contoso-pro
Action: 'Swap Slots'
WebAppName: func-contoso-tarjetas-pro
SourceSlot: preproduccionApplication Insights viene integrado y es donde se ve todo: ejecuciones con éxito y con error, duración, telemetría de dependencias y la traza de extremo a extremo desde la cola hasta el blob. Su explotación es 07-03; aquí basta con saber que se habilita al crear la aplicación y que sin él una función que falla es una caja negra.
- Cuándo NO usar funciones
- Procesos de larga duración con estado en memoria: usa Durable Functions o un contenedor. Y cargas constantes y altas: si el código se ejecuta sin parar, un plan fijo o Container Apps sale más barato que pagar por ejecución.
- Latencia mínima garantizada en la primera petición: el arranque en frío del plan de Consumo no es aceptable para la ruta de compra.
- Aplicaciones con mucha lógica compartida y despliegue conjunto: si las «funciones» son un monolito troceado que siempre se despliega junto, es una API, y su sitio es App Service o Container Apps. Lo mismo con conexiones persistentes o dependencias pesadas de arranque: cada instancia nueva paga ese arranque, y con escalado agresivo el destino sufre.
Errores Comunes y Consejos
- No hacer la función idempotente. Se manifiesta como duplicados esporádicos imposibles de reproducir. Diséñalo desde el primer día.
- Usar cadenas de conexión con clave en los ajustes de
Connection. Usa el sufijo__blobServiceUricon identidad administrada. - Meter la carga completa en el mensaje. Límite de 64 KB en colas de Storage: manda la referencia.
- Escribir código no determinista en un orquestador.
DateTime.NowoGuid.NewGuid()producen resultados incoherentes al reanudar. - Ignorar la cola de mensajes fallidos, donde acaban los mensajes envenenados; si nadie la mira, los errores son invisibles. Y no limitar la concurrencia cuando la función escribe en
db-reservas: mil instancias agotan el grupo de conexiones. - Confirmar
local.settings.json. Contiene credenciales locales. Compruébalo en la revisión de 05-02. - Consejo: mide la duración en Application Insights; bajar una función de 4 s a 1,5 s reduce la factura de Consumo casi en la misma proporción. Y una función, una responsabilidad: si tiene tres desencadenadores lógicos distintos, son tres funciones.
Ejercicios
Ejercicio 1. Contoso Millas necesita una función que, cuando un pasajero completa un vuelo, sume las millas correspondientes a su perfil en cosmos-contoso-tarifas-pro. Los eventos llegan por cola-vuelos-completados.
- Elige desencadenador, enlaces y plan de hospedaje, y justifícalo.
- El primer día en producción, algunos pasajeros aparecen con las millas duplicadas. Explica la causa exacta, da dos formas de arreglarlo, e indica las etiquetas del recurso y la alerta que configurarías desde el día uno.
Ejercicio 2. Diseña con Durable Functions el proceso de reembolso de un billete: validar la solicitud, comprobar en paralelo penalización y saldo de la pasarela, esperar la aprobación de un supervisor (máximo 72 horas) y, si se aprueba, ejecutar el abono y notificar.
- Indica qué patrón cubre cada tramo y por qué no se puede resolver con una función normal en el plan de Consumo.
- Escribe en pseudocódigo la espera del supervisor con su caducidad y di qué pasa si el orquestador se reinicia mientras espera.
Ejercicio 3. Una función con desencadenador HTTP consulta db-reservas y responde en 200 ms cuando hay poco tráfico, pero al abrir la venta de plazas devuelve errores de tiempo de espera de la base de datos.
- Explica el mecanismo del fallo.
- Propón tres medidas correctoras.
Soluciones
Solución 1:
- Desencadenador de cola de Storage sobre
cola-vuelos-completados, con enlace de salida a Cosmos DB al contenedorperfiles. Plan Flex Consumo: volumen a ráfagas concentradas al aterrizar los vuelos, coste casi cero de madrugada, y necesita red virtual para llegar al punto privado de Cosmos. - La causa es la entrega al menos una vez: si la función suma las millas y se cae antes de confirmar el mensaje, este reaparece y se vuelve a sumar. Sumar es la operación no idempotente por excelencia. Arreglo (a): guardar en el documento del perfil el conjunto de identificadores de vuelo ya acreditados y descartar el evento si ya está —comprobación previa dentro de la misma escritura—. Arreglo (b): registrar el identificador del mensaje en la tabla
registroembarquesantes de sumar, y descartar si ya existe. La (a) es preferible porque no añade otro almacén ni otra ventana de fallo. Etiquetas:entorno,proyecto=contoso-millas,centro-coste=CC-2077ypropietario. La alerta del primer día es sobre la longitud de la cola de mensajes fallidos: en cuanto crece, hay mensajes envenenados y pasajeros sin sus millas.
Solución 2:
- Validar → comprobar → abonar → notificar es encadenamiento; penalización y saldo en paralelo son fan-out / fan-in; la aprobación del supervisor es esperar a un evento externo. No cabe en una función normal porque la espera dura hasta 72 horas y el plan de Consumo corta a los 5 o 10 minutos; además haría falta persistir el punto exacto del proceso, y una función normal es sin estado.
var evento = contexto.WaitForExternalEvent<Decision>("AprobacionSupervisor");junto avar plazo = contexto.CreateTimer(contexto.CurrentUtcDateTime.AddHours(72), cts.Token);y unTask.WhenAnysobre ambos; si gana el plazo se rechaza por caducidad, y si gana el evento se cancela el temporizador. Si el orquestador se reinicia mientras espera, no pasa nada: la instancia no está ocupando cómputo, su historial está persistido y al reanudar se reproduce hasta el punto de espera. Esa es exactamente la propiedad que aporta el modelo duradero.
Solución 3:
- La función escala a decenas o cientos de instancias en paralelo, y cada instancia abre sus propias conexiones a
db-reservas. El grupo de conexiones del servidor se agota, las peticiones se encolan y acaban expirando. El síntoma aparece en la base de datos, pero la causa es el escalado descontrolado de la función. - (a) Limitar la concurrencia en
host.jsony fijarfunctionAppScaleLimita un techo compatible con la base de datos. (b) Reutilizar el cliente de base de datos como estático o singleton en lugar de crear uno por invocación, para que las conexiones se agrupen. (c) Quitar la sincronía del camino: encolar la petición y responder de inmediato, o poner una caché delante para las consultas repetidas. Como refuerzo, revisar el nivel desql-contoso-reservas-pro(03-02), aunque escalar la base de datos sin arreglar la concurrencia solo mueve el límite.
Conclusión
Ya sabes qué significa de verdad «sin servidor»: no hay máquina que elegir ni parchear, se paga por ejecuciones y gigabytes-segundo, una función parada cuesta cero y una función lenta cuesta más. Tienes interiorizado el modelo mental de desencadenadores y enlaces —un desencadenador que decide cuándo, enlaces de entrada y salida que eliminan la fontanería de conectarse, autenticarse y reintentar— y conoces los más usados con su encaje en Contoso. Sabes comparar los planes de hospedaje por arranque en frío, escalado, red virtual, duración y coste, y por qué las funciones de Contoso van en Flex Consumo mientras la orquestación del check-in necesita Premium.
Has escrito las dos funciones reales: GenerarTarjetaEmbarque, disparada por cola-emision-tarjetas, con enlace de salida al contenedor tarjetas-embarque mediante una plantilla de nombre determinista que regala la idempotencia; y SincronizarTarifas, alimentada por la fuente de cambios de cosmos-contoso-tarifas-pro con su contenedor de arrendamientos y su procesamiento por lotes. Las has configurado con conexiones por identidad (__blobServiceUri, __queueServiceUri) y referencias a kv-contoso-pro, sin una sola clave. Con Durable Functions has orquestado el check-in aplicando encadenamiento, fan-out/fan-in y espera de evento externo durante 24 horas sin consumir cómputo, respetando la regla del orquestador determinista. Y te llevas la lección dura: al menos una vez, no exactamente una vez, con sus tres estrategias de idempotencia, la cola de mensajes fallidos que hay que vigilar, los tiempos de espera, el límite de 64 KB y la concurrencia que hay que frenar cuando detrás hay una base de datos. Sabes desplegar con ranuras desde el pipeline, supervisar con Application Insights (07-03) y, sobre todo, cuándo no usar funciones.
Queda una pieza incómoda. Cuando un vuelo se retrasa, Contoso tiene que buscar los pasajeros afectados en db-reservas, avisarles por correo y por mensaje, publicar en el canal del equipo de operaciones y abrir un incidente en el sistema de soporte. Nada de eso es lógica de negocio compleja: es pegar sistemas entre sí, y escribirlo como código significa mantener a mano cuatro integraciones con sus autenticaciones, sus reintentos y sus formatos. Además, quien conoce ese proceso no es Diego, es el equipo de operaciones, que no programa. La lección siguiente, Azure Logic Apps, es exactamente para eso: flujos de trabajo visuales sobre un catálogo de más de mil conectores, con su definición en JSON versionable, su control de flujo, su gestión de errores y su historial de ejecuciones, y con el criterio para saber cuándo toca una aplicación lógica, cuándo una función y cuándo Data Factory.
Curso de Azure
Módulo 1: Introducción a Azure
- ¿Qué es Azure?
- Modelos de servicio, regiones y zonas de disponibilidad
- Crear y configurar tu cuenta de Azure
- Recorrido por el portal de Azure
- Azure Resource Manager: suscripciones, grupos de recursos y etiquetas
- Azure CLI, PowerShell y Cloud Shell
Módulo 2: Servicios principales de Azure
- Máquinas virtuales de Azure
- Escalado y alta disponibilidad del cómputo
- Azure App Service
- Azure Storage: blobs, archivos, colas y tablas
- Redes en Azure: redes virtuales, subredes y NSG
- Conectividad híbrida y entrega global
Módulo 3: Bases de datos de Azure
- Elegir el servicio de datos adecuado
- Azure SQL Database
- Azure Cosmos DB
- Azure Database for MySQL
- Azure Database for PostgreSQL
- Analítica de datos: Data Lake, Data Factory y Synapse
Módulo 4: Seguridad en Azure
- Microsoft Entra ID y gestión de identidades
- RBAC e identidades administradas
- Azure Key Vault
- Protección DDoS y firewall de aplicaciones web
- Microsoft Defender for Cloud
- Gobernanza y cumplimiento con Azure Policy
Módulo 5: Azure DevOps
- Introducción a Azure DevOps
- Azure Repos
- Azure Pipelines: integración continua
- Despliegue continuo con entornos y aprobaciones
- Azure Artifacts
- Infraestructura como código con Bicep
Módulo 6: Servicios avanzados de Azure
- Contenedores en Azure: Container Registry y Container Apps
- Azure Kubernetes Service (AKS)
- Azure Functions
- Azure Logic Apps
- Mensajería y eventos: Service Bus, Event Grid y Event Hubs
- Servicios de IA de Azure
Módulo 7: Monitoreo y gestión
- Azure Monitor: métricas, alertas y paneles
- Log Analytics y consultas KQL
- Application Insights
- Azure Automation y runbooks
- Copias de seguridad y recuperación ante desastres
Módulo 8: Gestión y optimización de costos
- Calculadora de precios y estimación de costes
- Azure Cost Management: análisis, presupuestos y alertas
- Reservas, planes de ahorro y Azure Hybrid Benefit
- Azure Advisor
- Estrategias de optimización y cultura FinOps
