Skip to content

mikezaschka/cds-caching

Repository files navigation

Welcome to cds-caching

npm version monthly downloads

Overview

A caching plugin for the SAP Cloud Application Programming Model (CAP) that improves performance by caching slow remote service calls, complex operations, and queries.

Please also read the introduction blog post: Boosting performance in SAP Cloud Application Programming Model (CAP) applications with cds-caching.

Key Features

  • Read-Through Caching – Transparently cache CQN queries, CAP requests, or function calls
  • Pluggable Storage – In-memory, SQLite, Redis, PostgreSQL, SAP HANA, or CDS database
  • Multi-Tenancy – Automatic tenant isolation for SAP BTP MTX deployments
  • TTL & Tag Support – Time-based expiry and tag-based invalidation
  • Compression – LZ4 or GZIP compression for cached data
  • Metrics & Monitoring – Hit rates, latencies, key-level tracking, and an OData API
  • Annotations – Declarative caching via @cache annotations on entities and functions

Documentation

Guide Description
Programmatic API Full API reference for cache operations
Key Management Key templates, context awareness, custom keys
Metrics Guide Statistics, monitoring, and performance tracking
OpenTelemetry Integration Distributed tracing and metrics export
OData API REST endpoints for management and monitoring
Dashboard Setup and usage of the monitoring dashboard
Deployment Guide SAP BTP deployment for Redis, PostgreSQL, HANA, CDS
Migration Guide Upgrading from 0.x to 1.x, 1.1 to 1.2, and 1.2.x to 1.3.0
Example Application Sample app with caching patterns

Getting Started

Installation

npm install cds-caching

Minimal Configuration

{
  "cds": {
    "requires": {
      "caching": {
        "impl": "cds-caching"
      }
    }
  }
}

This uses the in-memory store — no additional setup needed for development.

Data Model

The plugin ships CDS entity definitions for database-backed features. These are auto-loaded conditionally based on your configuration — no manual model property or using from needed:

Condition Entities loaded Purpose
statistics block present Caches, Metrics, KeyMetrics Persist metrics and runtime config to the database
store: 'cds' CacheStore Key-value table used by the CDS store adapter
using from 'cds-caching/index.cds' CachingApiService + statistics entities OData API for the dashboard
None of the above Nothing Plugin works with external stores only

The auto-loading works by injecting the relevant CDS files into cds.env.roots at plugin load time, before CAP compiles the model. This means cds deploy and cds build automatically pick up the required tables.

Basic Usage

const cache = await cds.connect.to("caching")

// Key-value operations
await cache.set("bp:1000001", businessPartnerData, { ttl: 60000 })
const data = await cache.get("bp:1000001")

// Read-through caching for CQN queries
const { result } = await cache.rt.run(query, db, { ttl: 30000 })

// Read-through caching for remote services
const { result } = await cache.rt.send(request, remoteService, { ttl: 10000 })

// Function caching
const cachedFn = cache.rt.wrap("expensive-op", expensiveFunction, { ttl: 3600 })
const { result } = await cachedFn("param1")

Annotation-Based Caching

service MyService {
  @cache: { ttl: 10000 }
  entity Products as projection on db.Products;

  @cache: { ttl: 10000, invalidateOnWrite: true }
  entity Orders as projection on db.Orders;

  @cache: { ttl: 60000 }
  function getRecommendations() returns array of Products;
}

When invalidateOnWrite is set, the cache for that entity is automatically cleared after any CREATE, UPDATE, or DELETE operation, so subsequent reads always return fresh data.

Configuration

Store Types

Store Config Use Case Adapter Package
In-Memory "memory" Development, small-scale Built-in
SQLite "sqlite" Medium-size, single instance @resolid/keyv-sqlite or @keyv/sqlite
Redis "redis" Production, distributed @keyv/redis
PostgreSQL "postgres" Production, when Redis unavailable @keyv/postgres
CDS Database "cds" Production, HANA, multi-tenant None (uses app's DB)
SAP HANA "hana" Direct HANA connection keyv-hana

Recommendation: Use store: 'cds' for CAP applications on SAP HANA — it reuses your app's DB connection, requires no extra packages, and supports multi-tenancy automatically. Use store: 'redis' for best performance in distributed setups.

Full Configuration Options

{
  "cds": {
    "requires": {
      "caching": {
        "impl": "cds-caching",
        "namespace": "caching",
        "store": "redis",
        "compression": "lz4",
        "throwOnErrors": false,
        "transactionalOperations": false,
        "credentials": { },
        "statistics": {
          "enabled": true,
          "persistenceInterval": 60000
        },
        "keyManagement": {
          "isUserAware": false,
          "isTenantAware": false,
          "isLocaleAware": false
        }
      }
    }
  }
}
Option Default Description
store "memory" Storage backend (memory, sqlite, redis, postgres, hana, cds)
namespace service name Key prefix for store isolation
compression none "lz4" or "gzip"
throwOnErrors false Whether basic operations throw on cache errors
transactionalOperations false Isolate basic ops in dedicated cache transactions
statistics none When present, auto-loads the statistics data model and enables persistence (see Statistics & Monitoring)
statistics.enabled false Enable metrics collection
statistics.persistenceInterval 60000 Interval (ms) for persisting hourly stats to the database
keyManagement.isTenantAware false (auto true in MTX) Include tenant in cache keys
keyManagement.isUserAware false Include user in cache keys
keyManagement.isLocaleAware false Include locale in cache keys

Environment-Specific Configuration

{
  "cds": {
    "requires": {
      "caching": {
        "impl": "cds-caching",
        "store": "redis",
        "[development]": {
          "credentials": { "host": "localhost", "port": 6379 }
        },
        "[production]": {
          "credentials": { "url": "redis://production-redis:6379" }
        }
      }
    }
  }
}

For detailed key configuration and deployment instructions, see Key Management and Deployment Guide.

Service Integration

The plugin includes CachingApiService, an OData service for managing caches, browsing entries, and viewing metrics. It powers the dashboard and can be consumed by any OData client.

The easiest way to set this up is cds add caching-dashboard, which creates both the service exposure and the dashboard UI. To expose only the service without the dashboard, reference it in one of your .cds files:

using {plugin.cds_caching.CachingApiService} from 'cds-caching/index.cds';

annotate CachingApiService with @requires: 'authenticated-user';

This automatically loads the required database entities (Caches, Metrics, KeyMetrics) via a transitive using from dependency — no additional configuration needed. Without this step, the service won't be served by CAP and the dashboard won't work.

Multi-Tenancy (MTX)

cds-caching supports SAP BTP multi-tenant applications using @sap/cds-mtxs. When multitenancy is detected, the plugin automatically:

  • Enables tenant-aware cache keys ({tenant}:{hash})
  • Defers database operations to request-time (avoids startup crashes without tenant context)
  • Guards statistics persistence to only run within a tenant request context

Recommended Setup

{
  "cds": {
    "requires": {
      "multitenancy": true,
      "caching": {
        "impl": "cds-caching",
        "store": "cds"
      }
    }
  }
}

With store: 'cds', each tenant's cache data lives in its own HDI container — fully isolated by CAP's Service Manager.

Alternatively, use store: 'redis' for shared Redis with automatic tenant-prefixed keys:

{
  "cds": {
    "requires": {
      "multitenancy": true,
      "caching": {
        "impl": "cds-caching",
        "store": "redis",
        "credentials": { "socket": { "host": "localhost", "port": 6379 } }
      }
    }
  }
}

isTenantAware is automatically set to true in MTX mode. Set "isTenantAware": false in keyManagement to explicitly opt out.

Usage Patterns

Deprecation Notice: cache.run(), cache.send(), cache.wrap(), cache.exec() are deprecated since v1.0. Use cache.rt.run(), cache.rt.send(), cache.rt.wrap(), cache.rt.exec() instead. See Migration Guide.

Read-Through Query Caching

const { result } = await cache.rt.run(
  SELECT.from(BusinessPartners).where({ type: '2' }), 
  db, 
  { ttl: 30000 }
)

Read-Through Remote Service Caching

this.on('READ', BusinessPartners, async (req, next) => {
  const bupa = await cds.connect.to('API_BUSINESS_PARTNER')
  const { result } = await cache.rt.run(req, bupa, { ttl: 30000 })
  return result
})

ApplicationService Caching with prepend

this.prepend(() => {
  this.on('READ', MyEntity, async (req, next) => {
    const cache = await cds.connect.to("caching")
    const { result } = await cache.rt.run(req, next)
    return result
  })
})

Function Caching

// Wrap: create a cached version of a function
const cachedFn = cache.rt.wrap("bp-data", fetchBPData, { ttl: 3600, tags: ['bp'] })
const { result } = await cachedFn("1000001", true)

// Exec: immediate one-off execution with caching
const { result } = await cache.rt.exec("product", fetchProduct, ["1000001"], { ttl: 3600 })

Cache Invalidation

// Time-based (TTL)
await cache.set("key", value, { ttl: 60000 })

// Key-based
await cache.delete("bp:1000001")

// Tag-based
await cache.set("bp:1000001", data, { tags: [{ value: "bp-list" }] })
await cache.set("bp:1000002", data, { tags: [{ value: "bp-list" }] })
await cache.deleteByTag("bp-list")

// Dynamic tags from data
await cache.set("bp-list", bpArray, { 
  tags: [{ data: "businessPartner", prefix: "bp-" }] 
})

Automatic Invalidation on Write

For annotation-based entity caching, use invalidateOnWrite to automatically clear all cached queries for an entity whenever its data changes:

@cache: { ttl: 10000, invalidateOnWrite: true }
entity CachedProducts as projection on db.Products;

This registers after handlers for CREATE, UPDATE, and DELETE that call deleteByTag with an entity-level tag. All cached variants (filtered, sorted, paginated, single-entity) are invalidated at once.

For more usage patterns, error handling details, and TypeScript support, see Programmatic API.

Statistics & Monitoring

To persist metrics to the database, add a statistics block to your configuration. This automatically loads the required data model (Caches, Metrics, KeyMetrics tables):

{
  "cds": {
    "requires": {
      "caching": {
        "impl": "cds-caching",
        "store": "redis",
        "statistics": {
          "enabled": true,
          "persistenceInterval": 60000
        }
      }
    }
  }
}

You can also enable metrics at runtime:

const cache = await cds.connect.to("caching")
await cache.setMetricsEnabled(true)
await cache.setKeyMetricsEnabled(true)

const stats = await cache.getCurrentMetrics()

To add the monitoring dashboard to your project, run:

cds add caching-dashboard

This copies a pre-built UI5 dashboard into your app/ folder and exposes the CachingApiService. After running cds watch, the dashboard is available at /caching-dashboard/index.html.

Cache Dashboard

See the Dashboard Guide for details on features, security, and customization.

See the full Metrics Guide →

API Reference

API Description
Programmatic API JavaScript methods for cache operations
OData API REST endpoints for monitoring and management

Contributing

Contributions are welcome! Please submit pull requests to the repository.

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

Plugin for SAP CAP providing a powerful and flexible caching service

Topics

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors