Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,18 @@ path = "examples/soluciones/managed_services_nivel_3.rs"
name = "serverless"
path = "examples/serverless.rs"

[[example]]
name = "serverless_nivel_1"
path = "examples/soluciones/serverless_nivel_1.rs"

[[example]]
name = "serverless_nivel_2"
path = "examples/soluciones/serverless_nivel_2.rs"

[[example]]
name = "serverless_nivel_3"
path = "examples/soluciones/serverless_nivel_3.rs"

[[example]]
name = "networking_nivel_1"
path = "examples/soluciones/networking_nivel_1.rs"
Expand Down
4 changes: 2 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,5 @@ esperada es:

## Siguiente paso natural

Completar el milestone `07. Serverless` con el flujo restante: ejercicios,
soluciones y análisis de costos.
Iniciar el milestone `08. Costos y FinOps` con concepto, problema, alternativas
e invariantes antes de escribir el modelo Rust mínimo.
16 changes: 15 additions & 1 deletion docs/07-serverless.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
- **Semestre:** 5
- **Estado:** implemented
- **Milestone:** 07. Serverless
- **Issues:** #25, #26, #27
- **Issues:** #25, #26, #27, #28
- **Módulo Rust:** `src/serverless.rs`
- **Diagrama:** `diagrams/07-serverless.mmd`
- **Ejemplo:** `examples/serverless.rs`
- **Ejercicios:** `docs/ejercicios/07-serverless.md`
- **Costos:** `docs/costos/07-serverless.md`

## Concepto

Expand Down Expand Up @@ -209,6 +211,15 @@ El ejemplo no contacta proveedores ni ejecuta funciones reales. Su intención es
mostrar qué decisiones deben quedar visibles antes de elegir un runtime
serverless.

## Ejercicios y costos

Los ejercicios viven en `docs/ejercicios/07-serverless.md` y tienen soluciones
compilables en `examples/soluciones/serverless_nivel_*.rs`.

El análisis de costos vive en `docs/costos/07-serverless.md`. No usa precios de
proveedor ni agrega benchmarks: compara elasticidad, límites, retries,
observabilidad, dependencia y carga downstream.

## Práctica sugerida

Antes de diseñar un flujo serverless, escribe:
Expand All @@ -226,6 +237,9 @@ Antes de diseñar un flujo serverless, escribe:
Si un flujo reintenta y escribe estado sin idempotencia, todavía no debería
considerarse listo para producción.

Los ejercicios graduados viven en `docs/ejercicios/07-serverless.md`. El
análisis de costos del capítulo vive en `docs/costos/07-serverless.md`.

## Decisiones registradas

- La unidad serverless se modela como `ServerlessWorkload` para cubrir función,
Expand Down
2 changes: 2 additions & 0 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
- [Ejercicios: servicios manejados](./ejercicios/06-servicios-manejados.md)
- [Costos: servicios manejados](./costos/06-servicios-manejados.md)
- [Serverless](./07-serverless.md)
- [Ejercicios: serverless](./ejercicios/07-serverless.md)
- [Costos: serverless](./costos/07-serverless.md)
- [Costos y FinOps](./08-costos-y-finops.md)
- [AWS en la práctica](./09-aws-en-la-practica.md)
- [GCP en la práctica](./10-gcp-en-la-practica.md)
55 changes: 55 additions & 0 deletions docs/costos/07-serverless.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Costos: serverless

- **Curso:** rust-cloud
- **Capítulo:** 07. Serverless
- **Estado:** implemented
- **Issue:** #28

Este capítulo no usa precios de proveedor. En serverless, el costo cambia por
invocaciones, duración, memoria, concurrencia, tráfico, eventos, logs, colas,
workflows, almacenamiento y región. Aquí el costo se expresa como elasticidad,
límites, retries, observabilidad, dependencia y carga downstream.

## Costos que sí se pueden comparar aquí

| Decisión | Costo visible | Riesgo si se ignora |
|----------|---------------|---------------------|
| Función HTTP | Pago por invocación y duración | Cold start, timeout y latencia variable |
| Handler de cola | Escalado por backlog | Duplicados si falta idempotencia |
| Concurrencia sin límite | Menos configuración inicial | Saturar bases, APIs o servicios dependientes |
| Retry automático | Recuperación ante fallas temporales | Multiplicar efectos secundarios y costo |
| Timeout alto | Menos fallas por tiempo | Ocultar procesos largos en runtime incorrecto |
| Observabilidad detallada | Mejor diagnóstico | Logs y métricas pueden crecer mucho |
| Workflow serverless | Orquestación visible | Costo por transición y acoplamiento al proveedor |

## Decisión sobre benchmarks

No se agrega `criterion` ni otro benchmark en este issue.

**Concepto:** el capítulo compara costos de ejecución por eventos: invocación,
duración, concurrencia, retries, observabilidad y carga sobre dependencias.

**Problema:** medir `ServerlessWorkload::evaluate` solo mediría operaciones
locales triviales. Ese número no enseña el costo real de serverless: cold
starts, duración, memoria, tráfico, transiciones, logs, escalado y saturación
de servicios downstream.

**Alternativas:** agregar `criterion`, simular millones de eventos o documentar
costos educativos sin dependencia externa.

**Justificación:** se documentan costos educativos sin dependencia externa. Un
benchmark será útil cuando el curso tenga simulaciones de tráfico, latencia,
colas, memoria, cold starts o FinOps. En este punto la evidencia correcta es
declarar límites, retries, idempotencia, estado y observabilidad.

## Checklist de costo antes de elegir serverless

- ¿El flujo es variable o constante?
- ¿Qué dispara la ejecución?
- ¿Cuánto dura y cuánta memoria necesita?
- ¿Qué límite de concurrencia protege a dependencias?
- ¿Qué pasa si el evento se procesa dos veces?
- ¿Cuánto cuesta observar cada ejecución?
- ¿Qué servicios downstream se saturan al escalar?
- ¿Qué partes quedan acopladas a eventos o workflows de proveedor?
- ¿Qué alternativa con capacidad estable sería más simple o barata?
107 changes: 107 additions & 0 deletions docs/ejercicios/07-serverless.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Ejercicios: serverless

- **Curso:** rust-cloud
- **Capítulo:** 07. Serverless
- **Estado:** implemented
- **Issue:** #28

Estos ejercicios practican serverless como contrato de ejecución por eventos.
La meta no es subir una función a un proveedor, sino declarar trigger, timeout,
concurrencia, retry, idempotencia, estado, observabilidad y costo.

## Ejercicio 1: handler de cola acotado `[Nivel 1]`

Construye un `ServerlessWorkload` para procesar eventos de publicación:

- `TriggerKind::Queue`;
- `RuntimeProfile::Function`;
- timeout de 30 segundos;
- concurrencia máxima de 50;
- retry con backoff;
- idempotencia por clave de evento;
- escritura en estado externo;
- observabilidad estándar.

**Entrada/Salida esperada:** la evaluación debe ser de bajo riesgo educativo.

<details>
<summary>Pista</summary>
El retry no es el problema por sí solo. El riesgo aparece cuando puede repetir
efectos secundarios sin idempotencia.
</details>

Solución compilable: `examples/soluciones/serverless_nivel_1.rs`.

## Ejercicio 2: detectar función riesgosa `[Nivel 2]`

Modela una función HTTP de pago que:

- tarda hasta 120 segundos;
- no declara concurrencia máxima;
- reintenta dos veces;
- no tiene idempotencia;
- escribe estado externo;
- no declara observabilidad.

**Entrada/Salida esperada:** la evaluación debe reportar retry sin
idempotencia, escritura de estado sin idempotencia, concurrencia sin límite,
timeout alto y observabilidad ausente.

<details>
<summary>Pista</summary>
Una función pequeña puede tener un efecto enorme si escribe estado y se repite.
</details>

Solución compilable: `examples/soluciones/serverless_nivel_2.rs`.

## Ejercicio 3: comparar scheduler y workflow `[Nivel 3]`

Construye dos workloads:

- un scheduler de limpieza sin estado, con concurrencia baja y sin retry;
- un workflow de publicación con retry, compensación y observabilidad estándar.

Compara sus evaluaciones.

**Entrada/Salida esperada:** ambos deben quedar sin hallazgos educativos si
declaran timeout, concurrencia, propósito y observabilidad completa.

<details>
<summary>Pista</summary>
Serverless no es solo funciones HTTP. Schedulers y workflows también deben
tener límites y señales observables.
</details>

Solución compilable: `examples/soluciones/serverless_nivel_3.rs`.

## Ejercicio 4: diseñar matriz serverless `[Nivel 4]`

Escribe una matriz para una plataforma educativa con:

- webhook de inscripción;
- procesador de publicaciones;
- generación programada de reportes;
- workflow de publicación;
- función de limpieza;
- evento de almacenamiento para assets.

Para cada flujo, declara evento, runtime, timeout, concurrencia, retry,
idempotencia, estado, observabilidad y costo/riesgo.

**Entrada/Salida esperada:** una tabla con nueve columnas: flujo, evento,
runtime, timeout, concurrencia, retry, idempotencia, estado y costo/riesgo.

<details>
<summary>Pista</summary>
El mejor diseño no es "todo serverless". Es saber qué flujos se benefician de
eventos elásticos y cuáles necesitan capacidad estable, latencia predecible o
menor acoplamiento.
</details>

Discusión sugerida:

- Los handlers de cola deben ser idempotentes.
- Las funciones HTTP de baja latencia deben cuidar cold start y timeout.
- Los schedulers deben declarar qué pasa si una ejecución se solapa.
- Los workflows ayudan a orquestar, pero pueden acoplarse fuerte al proveedor.
- La observabilidad debe permitir seguir un evento, no solo ver logs sueltos.
6 changes: 6 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ Los ejemplos progresivos de cada capítulo vivirán aquí:
delegada, estado durable y recuperación probada.
- `serverless.rs`: compara un flujo serverless acotado con una función que
revela riesgos de retry, estado y observabilidad.
- `soluciones/serverless_nivel_1.rs`: solución del ejercicio básico de handler
de cola acotado.
- `soluciones/serverless_nivel_2.rs`: solución del ejercicio intermedio de
función riesgosa.
- `soluciones/serverless_nivel_3.rs`: solución del ejercicio avanzado de
scheduler y workflow.
- `soluciones/managed_services_nivel_1.rs`: solución del ejercicio básico de
base de datos manejada con restauración probada.
- `soluciones/managed_services_nivel_2.rs`: solución del ejercicio intermedio
Expand Down
24 changes: 24 additions & 0 deletions examples/soluciones/serverless_nivel_1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use rust_cloud::serverless::{
IdempotencyStrategy, ObservabilityPlan, RetryPolicy, RuntimeProfile, ServerlessRequirements,
ServerlessWorkload, StateAccess, TriggerKind,
};

fn main() {
let workload = ServerlessWorkload::new(
"process-publication",
ServerlessRequirements {
trigger: TriggerKind::Queue,
runtime: RuntimeProfile::Function,
timeout_seconds: Some(30),
max_concurrency: Some(50),
retry_policy: RetryPolicy::Backoff { attempts: 3 },
idempotency: IdempotencyStrategy::EventKey,
state_access: StateAccess::ExternalWrite,
observability: ObservabilityPlan::standard(),
purpose: "procesar eventos de publicación de contenido",
},
)
.unwrap();

assert!(workload.evaluate().is_low_risk());
}
37 changes: 37 additions & 0 deletions examples/soluciones/serverless_nivel_2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use rust_cloud::serverless::{
IdempotencyStrategy, ObservabilityPlan, RetryPolicy, RuntimeProfile, ServerlessFinding,
ServerlessRequirements, ServerlessWorkload, StateAccess, TriggerKind,
};

fn main() {
let workload = ServerlessWorkload::new(
"charge-payment",
ServerlessRequirements {
trigger: TriggerKind::Http,
runtime: RuntimeProfile::Function,
timeout_seconds: Some(120),
max_concurrency: None,
retry_policy: RetryPolicy::Fixed { attempts: 2 },
idempotency: IdempotencyStrategy::None,
state_access: StateAccess::ExternalWrite,
observability: ObservabilityPlan::none(),
purpose: "registrar pago de estudiante",
},
)
.unwrap();
let findings = workload.evaluate().findings().to_vec();

assert!(
findings.contains(&ServerlessFinding::RetryWithoutIdempotency(
"charge-payment",
))
);
assert!(
findings.contains(&ServerlessFinding::StatefulWriteWithoutIdempotency(
"charge-payment",
))
);
assert!(findings.contains(&ServerlessFinding::UnboundedConcurrency("charge-payment",)));
assert!(findings.contains(&ServerlessFinding::HighFunctionTimeout("charge-payment",)));
assert!(findings.contains(&ServerlessFinding::MissingObservability("charge-payment",)));
}
41 changes: 41 additions & 0 deletions examples/soluciones/serverless_nivel_3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use rust_cloud::serverless::{
IdempotencyStrategy, ObservabilityPlan, RetryPolicy, RuntimeProfile, ServerlessRequirements,
ServerlessWorkload, StateAccess, TriggerKind,
};

fn main() {
let cleanup = ServerlessWorkload::new(
"cleanup-expired-sessions",
ServerlessRequirements {
trigger: TriggerKind::Schedule,
runtime: RuntimeProfile::Function,
timeout_seconds: Some(20),
max_concurrency: Some(1),
retry_policy: RetryPolicy::None,
idempotency: IdempotencyStrategy::None,
state_access: StateAccess::ExternalRead,
observability: ObservabilityPlan::standard(),
purpose: "limpiar sesiones expiradas",
},
)
.unwrap();

let workflow = ServerlessWorkload::new(
"publish-course-workflow",
ServerlessRequirements {
trigger: TriggerKind::DomainEvent,
runtime: RuntimeProfile::Workflow,
timeout_seconds: Some(300),
max_concurrency: Some(10),
retry_policy: RetryPolicy::Backoff { attempts: 3 },
idempotency: IdempotencyStrategy::Compensation,
state_access: StateAccess::ExternalWrite,
observability: ObservabilityPlan::standard(),
purpose: "orquestar publicación de capítulo validado",
},
)
.unwrap();

assert!(cleanup.evaluate().is_low_risk());
assert!(workflow.evaluate().is_low_risk());
}
Loading