utils-decorators icon indicating copy to clipboard operation
utils-decorators copied to clipboard

Cache burst for memoize/memoizeAsync

Open rdhainaut opened this issue 2 years ago • 9 comments

First, thanks for your work and this library.

Please is it possible to provide a sample how to "burst"/invalidate the cache when we use memoize/memoizeAsync function/decorator ?

Thank you

rdhainaut avatar Oct 14 '21 09:10 rdhainaut

hi @rdhainaut, for both decorators you can pass your own cache object (see here https://vlio20.github.io/utils-decorators/#memoize) under the cache attribute of the config object. That way you can control the cache as you wish. Does it helps?

vlio20 avatar Oct 14 '21 09:10 vlio20

Yeah it helps. I see you expose a generic interface and so I can control the cache behavior. I could use the delete method from Cache object to invalidate the cache.

Do you have considered to add a @BurstMemoizeAsync (or @InvalidateMemoizeAsync) to handle the case of cache invalidation for api calls for example ?

rdhainaut avatar Oct 14 '21 11:10 rdhainaut

Do you mean to have a decorator to which you provide the same cache as you are providing to the memorize decorator?

vlio20 avatar Oct 14 '21 11:10 vlio20

I have write a sample to explain better what i m trying to achieve. All explanations are in comments.

Note: I have choose the name "@burstMemoizeAsync" but it could be "@InvalidateMemoizeAsync" or "@FreeMemoizeAsync" or "@ClearMemoizeAsync". Chose a good name is the hardest thing in DEV :)

import { memoizeAsync } from "utils-decorators";

class LikesCounter {
  /** 
   * Description: Increase the likes counter and return the new total of likes
   */
  public async ILike(): Promise<number>{
    // INITIAL VALUE for likes counter = 100

    await this.IncrementLikesCount(); // likes counter has been incremented
    return await this.FetchLikesCount(); // EXPECTED VALUE for likes counter = 101
  }

  @memoizeAsync()
  public async FetchLikesCount(): Promise<number> {
    const url = 'https://www.api.com/Likes';

    const response = await fetch(url);
    const count = await response.text();
    return parseInt(count);
  }

  /** 
   * The decorator @burstMemoizeAsync clear previous cached value by @memoizeAsync. 
   * In other words, when i call this method i want "invalidate" the cache (= delete cached value) 
   */
  @burstMemoizeAsync({
    keyResolver: "FetchLikesCount"
  })
  public async IncrementLikesCount(){
    const url = 'https://www.api.com/Likes/PlusOne';

    return await fetch(url, { method: 'POST' });
  }
}

rdhainaut avatar Oct 15 '21 09:10 rdhainaut

I understand your intent but there has to be a way to connect between the two decorators memoizeAsync and burstMemoizeAsync. What will happen if you have 2 methods in your class that are decorated with memoizeAsync how will you know which cache to clear when burstMemoizeAsync will be called.

I don't see a way to do it with couple the two decorators.

vlio20 avatar Oct 15 '21 11:10 vlio20

First of all, thanks to take the time to answer me. My initial post was just a question. Now it s more a "feature request". You have already done an correct answer to use the Cache object directly but i want just see if it could be something usefull to add a new decorator to handle that case.

Just to be complete You should not have that issue that you re talking because the parameter keyResolver is required for decorator @burstMemoizeAsync (of course you must use the keyResolver that target the method with @MemoizeAsync) I suppose that from the name of method, you are able to retrieve the cache entry object.

@memoizeAsync()
public async FetchLikesCount(): Promise<number> { }
  
@burstMemoizeAsync({
  keyResolver: "FetchLikesCount" // WARNING we must use here the name of method that is decorated by @memoizeAsync decorator. In other word, you say here explicitely the name of methods where you want invalidate the cache.
})
public async IncrementLikesCount() { }

rdhainaut avatar Oct 15 '21 13:10 rdhainaut

The key resolver is responsible for setting the key in the cache. It is agnostic to the cache it is working with. An alternative (if you don't pass your own cache") could be is to add a new parameter called "cacheName" and provide it to both decorators. WDYT?

vlio20 avatar Oct 15 '21 14:10 vlio20

The cache is not a global object with "@memoizeAsync" ? If yes, why @bustMemoizeAsync cannot access to it ? If no, can we imported the globalCache in the class ?

// Solution 1
class LikeCounter {  
  @memoizeAsync() // use implicitly "global cache"
  public async FetchLikesCount(): Promise<number> { }
  
 @burstMemoizeAsync({  // use implicitly "global cache"
    keyResolver: "FetchLikesCount" 
  })
  public async IncrementLikesCount() { }
}
// Solution 2
import { cacheGlobal } from "utils-decorator/memoizeAsync";

class LikeCounter {  
  @memoizeAsync({ cache: cacheGlobal}) // use explicitely global cache
  public async FetchLikesCount(): Promise<number> { }
  
 @burstMemoizeAsync({
  cache: cacheGlobal, // use explicitely global cache
  keyResolver: "FetchLikesCount" 
  })
  public async IncrementLikesCount() { }
}

or maybe use a "local" Cache

// Solution 2 bis
class LikeCounter {
  cacheInstance: Cache = new Cache();
  
  @memoizeAsync({ cache: cacheInstance }) // use explicitely local cache
  public async FetchLikesCount(): Promise<number> { }
  
 @burstMemoizeAsync({
    cache: cacheInstance, // use explicitely local cache
    keyResolver: "FetchLikesCount" 
  })
  public async IncrementLikesCount() { }
}

rdhainaut avatar Oct 15 '21 14:10 rdhainaut

It is not a global object as you might have multiple caches. I think the best approach is the one that is already implemented with the cache property and the burstMemoizeAsync decorator can share it with the memoizeAsync or memoize decorators.

You are welcome to create a PR. Let me know if you need any help with that.

vlio20 avatar Oct 15 '21 14:10 vlio20