Construyendo Aplicaciones Multitenant Serverless con AWS Lambda y DynamoDB
Este tutorial práctico detalla cómo construir una arquitectura multitenant (multiusuario) utilizando un modelo serverless con AWS Lambda y Amazon DynamoDB, implementando aislamiento de datos y seguridad avanzada.
🚀 Introducción al Desarrollo Multitenant Serverless
El diseño de aplicaciones multitenant (multiusuario) permite que una sola instancia de una aplicación sirva a múltiples organizaciones o clientes (tenants), manteniendo sus datos y configuraciones estrictamente aislados. Al combinar este patrón arquitectónico con la tecnología serverless de AWS, logramos una infraestructura capaz de escalar automáticamente, reducir costos operativos al mínimo y eliminar la necesidad de aprovisionar servidores dedicados por cliente.
En este tutorial completo, exploraremos cómo diseñar una estrategia de aislamiento de datos utilizando particionado lógico en Amazon DynamoDB y cómo validar tokens de autenticación en AWS Lambda para identificar dinámicamente al tenant en cada petición HTTP.
🛠️ Arquitectura y Estrategias de Aislamiento
Antes de escribir código, es fundamental comprender las diferentes estrategias disponibles para separar los datos y los recursos entre los distintos tenants en un entorno serverless.
| Estrategia de Aislamiento | Ventajas | Desventajas | Costo Operativo |
|---|---|---|---|
| --- | --- | --- | --- |
| Silos (Aislamiento Total) | Máxima seguridad y cumplimiento normativo | Costoso, despliegues complejos por cliente | Alto |
| Pools (Aislamiento Lógico) | Muy económico, fácil mantenimiento | Requiere lógica estricta de filtrado en código | Bajo |
| --- | --- | --- | --- |
| Modelo Híbrido | Flexibilidad para clientes VIP vs Estándar | Complejidad arquitectónica moderada | Medio |
Para este tutorial, utilizaremos el modelo de pools (aislamiento lógico) utilizando una tabla única en Amazon DynamoDB, donde la clave de partición incluirá el identificador del tenant.
📋 Prerrequisitos y Configuración del Entorno
Para seguir este tutorial paso a paso, necesitarás tener instalado y configurado lo siguiente:
- Una cuenta activa de AWS con permisos para crear funciones Lambda, tablas DynamoDB y roles IAM.
- Node.js (versión 18.x o superior) instalado en tu máquina local.
- AWS CLI configurado con tus credenciales (aws configure).
- El framework Serverless o AWS SAM para el despliegue de la infraestructura.
🗄️ Diseño del Modelo de Datos en DynamoDB
El núcleo de nuestra aplicación multitenant recae en cómo estructuramos las claves en Amazon DynamoDB. Utilizaremos una tabla llamada SaaSAppData con el siguiente esquema:
- Partition Key (PK):
TENANT#<tenant_id>(Ejemplo:TENANT#empresa_a) - Sort Key (SK):
METADATA#<resource_id>oUSER#<user_id>
Esta estructura nos permite realizar consultas rápidas y eficientes garantizando que ningún tenant pueda acceder accidentalmente a los datos de otro.
A continuación, definimos la infraestructura usando una plantilla de despliegue en formato YAML (Serverless Framework):
service: serverless-multitenant-app
provider:
name: aws
runtime: nodejs18.x
region: us-east-1
environment:
DYNAMODB_TABLE: ${self:custom.tableName}
iam:
role:
statements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:PutItem
- dynamodb:GetItem
- dynamodb:DeleteItem
Resource:
- arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/${self:custom.tableName}
custom:
tableName: SaaSAppData
resources:
Resources:
SaasDataTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:custom.tableName}
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: PK
AttributeType: S
- AttributeName: SK
AttributeType: S
KeySchema:
- AttributeName: PK
KeyType: HASH
- AttributeName: SK
KeyType: RANGE
🔐 Autenticación y Extracción del Tenant ID
Cada petición que llegue a nuestras funciones Lambda debe incluir un token JWT (JSON Web Token) emitido por nuestro servicio de autenticación (por ejemplo, AWS Cognito). El token contendrá una reclamación (claim) llamada custom:tenant_id.
Crearemos una función middleware o un decorador en Node.js que extraiga este valor y lo inyecte en el contexto de ejecución de la función Lambda.
'use strict';
const extractTenantId = (event) => {
try {
const claims = event.requestContext.authorizer.jwt.claims;
const tenantId = claims['custom:tenant_id'];
if (!tenantId) {
throw new Error('Tenant ID no encontrado en el token de autorización');
}
return tenantId;
} catch (error) {
throw new Error(`Error de autenticación multitenant: ${error.message}`);
}
};
module.exports = { extractTenantId };
⚡ Implementación de la Función Lambda Principal
Ahora desarrollaremos la lógica de negocio para crear y consultar registros asegurando el aislamiento estricto de los datos. La función utilizará el SDK de AWS para JavaScript (v3).
'use strict';
const { DynamoDBClient } = require("@aws-sdk/client-dynamodb");
const { DynamoDBDocumentClient, PutCommand, QueryCommand } = require("@aws-sdk/lib-dynamodb");
const { extractTenantId } = require("./auth");
const client = new DynamoDBClient({});
const dynamo = DynamoDBDocumentClient.from(client);
const TABLE_NAME = process.env.DYNAMODB_TABLE;
exports.handler = async (event) => {
console.log("Evento recibido:", JSON.stringify(event, null, 2));
try {
const tenantId = extractTenantId(event);
const httpMethod = event.requestContext.http.method;
const pk = `TENANT#${tenantId}`;
if (httpMethod === 'POST') {
const body = JSON.parse(event.body);
const sk = `RESOURCE#${body.resourceId}`;
const putParams = {
TableName: TABLE_NAME,
Item: {
PK: pk,
SK: sk,
data: body.data,
createdAt: new Date().toISOString(),
},
};
await dynamo.send(new PutCommand(putParams));
return {
statusCode: 201,
body: JSON.stringify({ message: 'Recurso creado exitosamente para el tenant', tenantId }),
};
}
if (httpMethod === 'GET') {
const queryParams = {
TableName: TABLE_NAME,
KeyConditionExpression: "PK = :pk",
ExpressionAttributeValues: {
":pk": pk,
},
};
const result = await dynamo.send(new QueryCommand(queryParams));
return {
statusCode: 200,
body: JSON.stringify({ tenantId, items: result.Items }),
};
}
return {
statusCode: 405,
body: JSON.stringify({ message: 'Método no permitido' }),
};
} catch (error) {
console.error("Error procesando la solicitud:", error);
return {
statusCode: 500,
body: JSON.stringify({ message: 'Error interno del servidor', error: error.message }),
};
}
};
📊 Pruebas y Validación del Aislamiento
Para verificar que nuestra arquitectura multitenant funciona correctamente y que ningún cliente puede ver los datos de otro, realizaremos un proceso de validación paso a paso:
🔍 Preguntas Frecuentes (FAQ)
¿Qué sucede si un tenant crece exponencialmente y consume todos los recursos de la tabla DynamoDB?
Al utilizar el modo de facturación On-Demand (Pay-Per-Request), DynamoDB escala automáticamente el rendimiento de lectura y escritura sin necesidad de aprovisionamiento manual, mitigando el problema del 'noisy neighbor' a nivel de almacenamiento y capacidad base.¿Es posible migrar un tenant del modelo Pool al modelo Silo en el futuro?
Sí, es posible. Diseñar la capa de acceso a datos utilizando un patrón repositorio o funciones abstractas facilita la migración de datos hacia una tabla o base de datos dedicada si un cliente enterprise requiere aislamiento físico total.✨ Conclusión y Próximos Pasos
¡Felicidades! Has implementado con éxito los fundamentos de una arquitectura multitenant altamente segura y escalable utilizando un enfoque serverless con AWS Lambda y Amazon DynamoDB. Esta configuración te permite crecer de manera ilimitada mientras mantienes costos operativos bajos y un aislamiento de datos estricto.
Como siguientes pasos recomendados, puedes explorar la integración de AWS WAF para protección contra ataques web y la implementación de límites de velocidad (Rate Limiting) por tenant utilizando Amazon API Gateway.
Tutoriales relacionados
- Asegurando Aplicaciones Serverless: Gestión de Secretos con AWS Secrets Managerintermediate18 min
- Construyendo un Backend Serverless con Google Cloud Functions y Firestoreintermediate20 min
- Simplificando la Integración de Datos Serverless con AWS Lambda y Amazon Kinesisintermediate20 min
- Gestionando el Estado en Aplicaciones Serverless con AWS Step Functions y DynamoDBintermediate20 min
- Automatización de Tareas Serverless con EventBridge: Diseñando Arquitecturas Basadas en Eventosintermediate15 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!