nestjs / nest

A progressive Node.js framework for building efficient, scalable, and enterprise-grade server-side applications with TypeScript/JavaScript 🚀
https://nestjs.com
MIT License
66.9k stars 7.55k forks source link

Method level @Cache annotations for Service #1032

Closed xmlking closed 6 years ago

xmlking commented 6 years ago

I'm submitting a...


[ ] Regression 
[ ] Bug report
[x] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.

Current behavior

Only cacheing implemented for controllers. missing auto cache invalidation

Expected behavior

proposed example:

@CacheConfig("pets")
class PetService {
    @Cacheable 
    findById(id: Long): Pet {
    }

    @CachePut
    save(pet: Pet) {
    }

    @CacheInvalidate
    delete(pet: Pet) {
    }
}

Minimal reproduction of the problem with instructions

What is the motivation / use case for changing the behavior?

we like to control caching and cache invalidation for CRUD Services automatically, key can be derived from args e.g., https://github.com/havsar/node-ts-cache/blob/master/src/CacheDecorator.ts

Environment


Nest version: X.Y.Z


For Tooling issues:
- Node version: XX  
- Platform:  

Others:

kamilmysliwiec commented 6 years ago

The way how internal CacheModule works differ from any typical class-oriented cache. What you have proposed can be implemented by any 3rd-party package which doesn't necessarily have to be only Nest compatible. For now, there is no plan to provide such functionality directly from the nest packages.

xmlking commented 6 years ago

are there any hooks to access underneath user configured cache store, so that we can implement custom annotations on top of cache store CacheModule provides ?

xmlking commented 5 years ago

Created this cache decorator. any feedback appreciated

https://github.com/xmlking/ngx-starter-kit/blob/develop/apps/api/src/app/cache/cache.decorator.ts

usage : https://github.com/xmlking/ngx-starter-kit/blob/develop/apps/api/src/app/external/weather/weather.service.ts#L37

angelnikolov commented 5 years ago

Hey guys, I also have something familiar, called ngx-cacheable. It's literally a Typescript decorator which work with both Observables and Promises. It was initially inspired by some of my work with Angular, however recently I also made it so it can be used with Promises and already use it on both the client and server of one of my apps :) @kamilmysliwiec, did you have any plans on supporting such a decorator as a first class citizen in Nest? (btw, I absolutely love the framework, already used in a couple of my public-facing projects :))

iam4x commented 5 years ago

Here is a simple @Cache decorator with lru-cache :

import LRU from 'lru-cache';

const cache = new LRU();

export function Cache({ key, ttl }: { key: string; ttl: number }) {
  return function(
    _target: Record<string, any>,
    _propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const method = descriptor.value;
    descriptor.value = async function(...args: any[]) {
      if (cache.get(key)) return cache.get(key);

      const result = await method.apply(this, args);
      cache.set(key, result, ttl);

      return result;
    };
  };
}

usage:

@Cache({ key: 'admin_menu_count', ttl: 1000 * 60 * 5 })
private async getCachedCount() {
    [...]
}
mallwang commented 5 years ago

@iam4x nice hint 👍

The issue I discovered using it at controller methods which return an observable was that it caches the original observable instead of the resolved value. In my case I request some data from another api using the HttpService, it caches the observable and passes it to nest which will resolve the observable every time I call the controller method, which again results in an http request for each call.

So I slightly modified your solution to be able to cache the value of the observable using the default cache-manager. Another modification I added was the option to calculate the ttl value during runtime by passing a function.

import * as cacheManager from 'cache-manager';
import {Logger} from '@nestjs/common';
import {of} from 'rxjs';

const memoryCache = cacheManager.caching({store: 'memory', max: 100, ttl: 10});

export function Cache({key, ttl}: {key?: string; ttl: number | Function}) {

  return function(target: Record<string, any>, propertyKey: string, descriptor: PropertyDescriptor) {
    if (!key) {
      key = `${target.constructor.name}/${propertyKey.toString()}`;
    }
    const method = descriptor.value;
    descriptor.value = async function(...args: any[]) {
      const cachedItem = await memoryCache.get(key);
      if (cachedItem) {
        return of(cachedItem); // return the cached value as observable
      }

      const result = await method.apply(this, args).toPromise();
      const calcTtl = typeof ttl === 'function' ? ttl() : ttl;
      await memoryCache.set(key, result, {ttl: calcTtl});
      return of(result); // return the value of the resolved result as observable
    };
  };
}

Usage at controller method level:

@Get('')
  @Cache({
    key: 'rush-hour',
    ttl: function() {
      // cache the value for the rest of the day because it gets once per day generated at midnight
      const now = new Date();
      return 24 * 60 * 60 - now.getHours() * 60 * 60 - now.getMinutes() * 60 - now.getSeconds();
    },
  })
  getRushHour(): Observable<RushHourResponse> {
    return this.rushHourService.find();
  }
lock[bot] commented 4 years ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.