Caché de Respuestas
Caché de Respuestas
Sección titulada «Caché de Respuestas»Aprende cómo implementar caché de respuestas en tus aplicaciones Veloce-TS para reducir latencia y carga en la base de datos.
Tabla de Contenidos
Sección titulada «Tabla de Contenidos»- Resumen
- Inicio Rápido
- Almacenes de Caché
- Decoradores
- Middleware
- Claves de Caché
- Configuración de TTL
- Invalidación de Caché
- Mejores Prácticas
Resumen
Sección titulada «Resumen»Veloce-TS proporciona un poderoso sistema de caché que te permite cachear operaciones costosas y reducir tiempos de respuesta. El sistema soporta:
- Almacén en Memoria - Caché rápido para aplicaciones de instancia única
- Almacén Redis - Caché distribuido para despliegues multi-instancia
- Soporte TTL - Configuración flexible de tiempo de vida
- Invalidación Basada en Patrones - Invalidar cachés relacionados eficientemente
- Headers de Caché - Headers automáticos
X-Cache: HIT/MISS
Inicio Rápido
Sección titulada «Inicio Rápido»1. Configuración Básica
Sección titulada «1. Configuración Básica»import { VeloceTS, CacheManager, MemoryCacheStore } from 'veloce-ts';
const app = new VeloceTS();
// Configurar almacén de cachéconst cacheStore = new MemoryCacheStore({ maxSize: 1000, // Máximo de entradas cleanupInterval: 60000 // Limpiar entradas expiradas cada 60s});
CacheManager.setDefaultStore(cacheStore);2. Cachear una Ruta
Sección titulada «2. Cachear una Ruta»import { Controller, Get, Cache } from 'veloce-ts';
@Controller('/products')class ProductController { @Get('/') @Cache({ ttl: '5m' }) // Cachear por 5 minutos async getAllProducts() { return await db.products.findAll(); }}Almacenes de Caché
Sección titulada «Almacenes de Caché»Almacén en Memoria
Sección titulada «Almacén en Memoria»Perfecto para aplicaciones de instancia única o desarrollo:
import { MemoryCacheStore, CacheManager } from 'veloce-ts';
const store = new MemoryCacheStore({ maxSize: 1000, // Max entradas (expulsión LRU) cleanupInterval: 60000 // Intervalo de limpieza en ms});
CacheManager.setDefaultStore(store);Características:
- Rápido (< 1ms tiempo de acceso)
- Expulsión LRU (Menos Recientemente Usado)
- Limpieza automática de entradas expiradas
- Sin dependencias externas
Almacén Redis
Sección titulada «Almacén Redis»Para aplicaciones distribuidas con múltiples instancias:
import { createRedisCacheStore, CacheManager } from 'veloce-ts';import { createClient } from 'redis';
// Usando paquete redisconst client = createClient({ url: 'redis://localhost:6379' });await client.connect();
const store = createRedisCacheStore(client, { prefix: 'myapp:cache:' // Prefijo de clave para namespacing});
CacheManager.setDefaultStore(store);// Usando paquete ioredisimport Redis from 'ioredis';
const client = new Redis('redis://localhost:6379');const store = createRedisCacheStore(client);Características:
- Caché distribuido entre instancias
- Caché persistente (sobrevive reinicios)
- ~2-5ms tiempo de acceso
- Soporta clustering
Decoradores
Sección titulada «Decoradores»@Cache()
Sección titulada «@Cache()»Cachear respuestas de rutas automáticamente:
@Controller('/api')class ApiController { // TTL simple @Get('/data') @Cache({ ttl: 300 }) // 5 minutos en segundos async getData() { return await fetchExpensiveData(); }
// TTL en formato string @Get('/products') @Cache({ ttl: '5m' }) // Soporta: '5s', '5m', '5h', '5d' async getProducts() { return await db.products.findAll(); }
// Clave de caché personalizada @Get('/user/:id') @Cache({ ttl: '10m', key: 'user:{id}' }) async getUser(@Param('id') id: string) { return await db.users.findById(id); }
// Caché con parámetros de query @Get('/search') @Cache({ ttl: '2m', includeQuery: true // Caché diferente para cada query }) async search(@Query() query: any) { return await db.products.search(query); }
// Caché condicional @Get('/items') @Cache({ ttl: '5m', condition: (result) => result.length > 0 // Solo cachear si hay resultados }) async getItems() { return await db.items.findAll(); }}Opciones:
interface CacheOptions { ttl: number | string; // Tiempo de vida key?: string; // Clave personalizada (soporta placeholders {param}) prefix?: string; // Prefijo de clave includeQuery?: boolean; // Incluir parámetros query en clave varyByHeaders?: string[]; // Variar por headers específicos condition?: (result) => boolean; // Condición de caché store?: CacheStore; // Almacén personalizado}@CacheInvalidate()
Sección titulada «@CacheInvalidate()»Invalidar caché después de mutaciones:
@Controller('/products')class ProductController { @Get('/') @Cache({ ttl: '5m', key: 'products:list' }) async list() { return await db.products.findAll(); }
@Post('/') @CacheInvalidate('products:*') // Limpiar todos los cachés de productos async create(@Body() data: CreateProductDTO) { return await db.products.create(data); }
@Put('/:id') @CacheInvalidate(['product:{id}', 'products:*']) // Múltiples patrones async update(@Param('id') id: string, @Body() data: UpdateProductDTO) { return await db.products.update(id, data); }
@Delete('/:id') @CacheInvalidate([ 'product:{id}', 'products:*', 'featured:*' ]) async delete(@Param('id') id: string) { await db.products.delete(id); return { success: true }; }}Middleware
Sección titulada «Middleware»Para rutas de API funcional:
Middleware de Caché
Sección titulada «Middleware de Caché»import { createCacheMiddleware } from 'veloce-ts';
app.get('/products', { middleware: [createCacheMiddleware({ ttl: '5m' })], handler: async () => { return await db.products.findAll(); }});
// Con clave personalizadaapp.get('/user/:id/posts', { middleware: [createCacheMiddleware({ ttl: 300, key: 'user:{id}:posts' })], handler: async (c) => { const id = c.req.param('id'); return await db.posts.findByUser(id); }});Middleware de Invalidación de Caché
Sección titulada «Middleware de Invalidación de Caché»import { createCacheInvalidationMiddleware } from 'veloce-ts';
app.post('/products', { middleware: [createCacheInvalidationMiddleware('products:*')], handler: async (c) => { const body = await c.req.json(); return await db.products.create(body); }});
// Múltiples patronesapp.put('/products/:id', { middleware: [createCacheInvalidationMiddleware([ 'product:{id}', 'products:*' ])], handler: async (c) => { const id = c.req.param('id'); const body = await c.req.json(); return await db.products.update(id, body); }});Claves de Caché
Sección titulada «Claves de Caché»Generación Automática
Sección titulada «Generación Automática»Las claves de caché se generan automáticamente desde:
- Método HTTP
- Ruta de la ruta
- Parámetros de ruta
- Parámetros query (si
includeQuery: true) - Headers (si
varyByHeadersespecificado)
Ejemplo:
// GET /products/123?sort=name// Clave por defecto: "get:/products/:id:{"id":"123"}"// Con includeQuery: "get:/products/:id:{"id":"123"}:{"sort":"name"}"Claves Personalizadas con Placeholders
Sección titulada «Claves Personalizadas con Placeholders»Usa placeholders para crear claves semánticas:
@Get('/products/:id')@Cache({ ttl: '5m', key: 'product:{id}' })async getProduct(@Param('id') id: string) { return await db.products.findById(id);}// La clave se convierte en: "product:123"
@Get('/user/:userId/posts/:postId')@Cache({ ttl: '5m', key: 'user:{userId}:post:{postId}' })async getPost(@Param('userId') userId: string, @Param('postId') postId: string) { return await db.posts.find(userId, postId);}// La clave se convierte en: "user:456:post:789"Prefijos de Clave
Sección titulada «Prefijos de Clave»Organiza tus claves de caché con namespaces:
@Cache({ ttl: '5m', prefix: 'api:v1', key: 'products:list'})// Clave final: "api:v1:products:list"Configuración de TTL
Sección titulada «Configuración de TTL»Formatos
Sección titulada «Formatos»// Segundos (número)@Cache({ ttl: 300 }) // 5 minutos
// Formato string@Cache({ ttl: '5s' }) // 5 segundos@Cache({ ttl: '5m' }) // 5 minutos@Cache({ ttl: '5h' }) // 5 horas@Cache({ ttl: '5d' }) // 5 díasRecomendaciones
Sección titulada «Recomendaciones»| Tipo de Dato | TTL | Ejemplo |
|---|---|---|
| Contenido estático | 1d - 7d | Páginas de ayuda, términos |
| Catálogo de productos | 5m - 1h | Productos, categorías |
| Datos de usuario | 1m - 5m | Perfil de usuario |
| Resultados de búsqueda | 30s - 2m | Queries de búsqueda |
| Datos en tiempo real | 10s - 30s | Precios de acciones |
Invalidación de Caché
Sección titulada «Invalidación de Caché»Coincidencia de Patrones
Sección titulada «Coincidencia de Patrones»Usa comodines para invalidar cachés relacionados:
// Limpiar todos los cachés de productosawait CacheManager.invalidate('products:*');
// Limpiar todos los cachés de usuarioawait CacheManager.invalidate('user:*');
// Limpiar patrón específicoawait CacheManager.invalidate('user:123:*');Invalidación Manual
Sección titulada «Invalidación Manual»import { CacheManager, invalidateCache, deleteCache, clearCache } from 'veloce-ts';
// Invalidar por patrónawait CacheManager.invalidate('products:*');await invalidateCache('products:*');
// Eliminar clave específicaawait CacheManager.delete('product:123');await deleteCache('product:123');
// Limpiar todo el cachéawait CacheManager.clear();await clearCache();Caché Programático
Sección titulada «Caché Programático»import { getCache, setCache } from 'veloce-ts';
// Obtener del cachéconst data = await getCache<Product[]>('products:list');
// Establecer en cachéawait setCache('products:list', products, '5m');
// Eliminar del cachéawait deleteCache('products:list');Headers de Caché
Sección titulada «Headers de Caché»Las respuestas incluyen automáticamente el estado del caché:
HTTP/1.1 200 OKX-Cache: HITX-Request-ID: abc-123-def-456Content-Type: application/jsono
HTTP/1.1 200 OKX-Cache: MISSX-Request-ID: abc-123-def-456Content-Type: application/jsonMejores Prácticas
Sección titulada «Mejores Prácticas»1. Cachear Operaciones de Lectura Intensiva
Sección titulada «1. Cachear Operaciones de Lectura Intensiva»✅ Buenos candidatos para caché:
- Catálogos de productos
- Perfiles de usuario (cuando no se actualizan frecuentemente)
- Resultados de búsqueda
- Datos agregados
- Respuestas de APIs externas
❌ No cachear:
- Datos sensibles específicos del usuario
- Datos en tiempo real (a menos que TTL muy corto)
- Datos que cambian frecuentemente
2. Usar TTL Apropiado
Sección titulada «2. Usar TTL Apropiado»// Datos estáticos - TTL largo@Cache({ ttl: '1d' })async getTermsOfService() { ... }
// Datos dinámicos - TTL corto@Cache({ ttl: '30s' })async getCurrentPrice() { ... }
// Datos de usuario - TTL medio@Cache({ ttl: '5m' })async getUserProfile() { ... }3. Invalidar en Mutaciones
Sección titulada «3. Invalidar en Mutaciones»Siempre invalida cachés relacionados después de actualizaciones:
@Post('/')@CacheInvalidate('products:*')async createProduct() { ... }
@Put('/:id')@CacheInvalidate(['product:{id}', 'products:*'])async updateProduct() { ... }4. Usar Coincidencia de Patrones Sabiamente
Sección titulada «4. Usar Coincidencia de Patrones Sabiamente»// Bien - Patrón específico@CacheInvalidate('products:category:electronics')
// Mejor - Más específico@CacheInvalidate('products:category:electronics:page:*')
// Demasiado amplio - Puede limpiar cachés no relacionados@CacheInvalidate('*')5. Monitorear Rendimiento del Caché
Sección titulada «5. Monitorear Rendimiento del Caché»const store = CacheManager.getDefaultStore() as MemoryCacheStore;const stats = store.getStats();console.log({ size: stats.size, maxSize: stats.maxSize, utilization: (stats.size / stats.maxSize) * 100});6. Usar Redis para Producción
Sección titulada «6. Usar Redis para Producción»Para despliegues multi-instancia:
// Desarrollo - Usar Almacén en Memoriaif (process.env.NODE_ENV === 'development') { CacheManager.setDefaultStore(new MemoryCacheStore());}
// Producción - Usar Redisif (process.env.NODE_ENV === 'production') { const redis = createClient({ url: process.env.REDIS_URL }); await redis.connect(); CacheManager.setDefaultStore(createRedisCacheStore(redis));}Ejemplo Completo
Sección titulada «Ejemplo Completo»import { VeloceTS, Controller, Get, Post, Put, Delete, Param, Body, Cache, CacheInvalidate, CacheManager, MemoryCacheStore} from 'veloce-ts';import { z } from 'zod';
// Configurar cachéconst cacheStore = new MemoryCacheStore({ maxSize: 1000 });CacheManager.setDefaultStore(cacheStore);
const ProductSchema = z.object({ name: z.string(), price: z.number()});
@Controller('/products')class ProductController { // Listar - cacheado por 5 minutos @Get('/') @Cache({ ttl: '5m', key: 'products:list' }) async list() { return await db.products.findAll(); }
// Obtener por ID - cacheado por 10 minutos @Get('/:id') @Cache({ ttl: '10m', key: 'product:{id}' }) async getById(@Param('id') id: string) { return await db.products.findById(id); }
// Buscar - cacheado por parámetros query @Get('/search') @Cache({ ttl: '2m', includeQuery: true }) async search(@Query() query: any) { return await db.products.search(query); }
// Crear - invalida caché de lista @Post('/') @CacheInvalidate('products:*') async create(@Body(ProductSchema) data: z.infer<typeof ProductSchema>) { return await db.products.create(data); }
// Actualizar - invalida producto específico y lista @Put('/:id') @CacheInvalidate(['product:{id}', 'products:*']) async update( @Param('id') id: string, @Body(ProductSchema) data: z.infer<typeof ProductSchema> ) { return await db.products.update(id, data); }
// Eliminar - invalida todos los cachés relacionados @Delete('/:id') @CacheInvalidate(['product:{id}', 'products:*']) async delete(@Param('id') id: string) { await db.products.delete(id); return { success: true }; }}
const app = new VeloceTS();app.include(ProductController);app.listen(3000);Próximos Pasos
Sección titulada «Próximos Pasos»- Aprende sobre Contexto de Request para rastreo de peticiones
- Explora Logging para depurar comportamiento del caché
- Revisa Mejores Prácticas de Rendimiento para optimización