logo

Service Class Pattern

A complete example of using the @cached decorator with a service class.

Full Example

import { cached } from '@humanspeak/memory-cache'

interface Product {
    id: string
    name: string
    price: number
    category: string
}

class ProductService {
    private db: Database

    constructor(db: Database) {
        this.db = db
    }

    @cached<Product | null>({ ttl: 300000 })  // 5 minutes
    async getProduct(id: string): Promise<Product | null> {
        return await this.db.products.findUnique({ where: { id } })
    }

    @cached<Product[]>({ ttl: 60000, maxSize: 100 })  // 1 minute
    async getProductsByCategory(category: string): Promise<Product[]> {
        return await this.db.products.findMany({
            where: { category },
            orderBy: { name: 'asc' }
        })
    }

    @cached<Product[]>({ ttl: 30000, maxSize: 50 })
    async searchProducts(query: string, limit: number): Promise<Product[]> {
        return await this.db.products.findMany({
            where: {
                name: { contains: query, mode: 'insensitive' }
            },
            take: limit
        })
    }

    // No caching for write operations
    async createProduct(data: Omit<Product, 'id'>): Promise<Product> {
        return await this.db.products.create({ data })
    }
}

// Usage
const productService = new ProductService(db)

// These are cached
const product = await productService.getProduct('prod-123')
const electronics = await productService.getProductsByCategory('electronics')
const results = await productService.searchProducts('laptop', 10)
import { cached } from '@humanspeak/memory-cache'

interface Product {
    id: string
    name: string
    price: number
    category: string
}

class ProductService {
    private db: Database

    constructor(db: Database) {
        this.db = db
    }

    @cached<Product | null>({ ttl: 300000 })  // 5 minutes
    async getProduct(id: string): Promise<Product | null> {
        return await this.db.products.findUnique({ where: { id } })
    }

    @cached<Product[]>({ ttl: 60000, maxSize: 100 })  // 1 minute
    async getProductsByCategory(category: string): Promise<Product[]> {
        return await this.db.products.findMany({
            where: { category },
            orderBy: { name: 'asc' }
        })
    }

    @cached<Product[]>({ ttl: 30000, maxSize: 50 })
    async searchProducts(query: string, limit: number): Promise<Product[]> {
        return await this.db.products.findMany({
            where: {
                name: { contains: query, mode: 'insensitive' }
            },
            take: limit
        })
    }

    // No caching for write operations
    async createProduct(data: Omit<Product, 'id'>): Promise<Product> {
        return await this.db.products.create({ data })
    }
}

// Usage
const productService = new ProductService(db)

// These are cached
const product = await productService.getProduct('prod-123')
const electronics = await productService.getProductsByCategory('electronics')
const results = await productService.searchProducts('laptop', 10)

With Hooks for Monitoring

import { cached } from '@humanspeak/memory-cache'

class ProductService {
    @cached<Product | null>({
        ttl: 300000,
        hooks: {
            onHit: ({ key }) => {
                metrics.increment('product_service.cache.hit', { method: 'getProduct' })
            },
            onMiss: ({ key }) => {
                metrics.increment('product_service.cache.miss', { method: 'getProduct' })
            }
        }
    })
    async getProduct(id: string): Promise<Product | null> {
        metrics.increment('product_service.db.query', { method: 'getProduct' })
        return await this.db.products.findUnique({ where: { id } })
    }

    @cached<Product[]>({
        ttl: 60000,
        maxSize: 100,
        hooks: {
            onHit: () => metrics.increment('product_service.cache.hit', { method: 'getByCategory' }),
            onMiss: () => metrics.increment('product_service.cache.miss', { method: 'getByCategory' }),
            onEvict: () => metrics.increment('product_service.cache.eviction', { method: 'getByCategory' })
        }
    })
    async getProductsByCategory(category: string): Promise<Product[]> {
        return await this.db.products.findMany({
            where: { category },
            orderBy: { name: 'asc' }
        })
    }
}
import { cached } from '@humanspeak/memory-cache'

class ProductService {
    @cached<Product | null>({
        ttl: 300000,
        hooks: {
            onHit: ({ key }) => {
                metrics.increment('product_service.cache.hit', { method: 'getProduct' })
            },
            onMiss: ({ key }) => {
                metrics.increment('product_service.cache.miss', { method: 'getProduct' })
            }
        }
    })
    async getProduct(id: string): Promise<Product | null> {
        metrics.increment('product_service.db.query', { method: 'getProduct' })
        return await this.db.products.findUnique({ where: { id } })
    }

    @cached<Product[]>({
        ttl: 60000,
        maxSize: 100,
        hooks: {
            onHit: () => metrics.increment('product_service.cache.hit', { method: 'getByCategory' }),
            onMiss: () => metrics.increment('product_service.cache.miss', { method: 'getByCategory' }),
            onEvict: () => metrics.increment('product_service.cache.eviction', { method: 'getByCategory' })
        }
    })
    async getProductsByCategory(category: string): Promise<Product[]> {
        return await this.db.products.findMany({
            where: { category },
            orderBy: { name: 'asc' }
        })
    }
}

Key Considerations

  • TTL by Method: Different methods may need different TTLs
  • Max Size: Limit based on expected unique argument combinations
  • Write Operations: Don’t cache write operations
  • Cache Invalidation: Consider how updates affect cached data