Coder Social home page Coder Social logo

Comments (12)

jonstorer avatar jonstorer commented on June 25, 2024 1

is there a way to make const cacheBuster$ = new Subject<void>(); specific to each instance of each child service?

from ts-cacheable.

angelnikolov avatar angelnikolov commented on June 25, 2024 1

Hey, thanks for your question. Will take a look after work :)

from ts-cacheable.

RobARichardson avatar RobARichardson commented on June 25, 2024

I ran into this exact same situation about a week ago. A solution to this would make this library even more amazing.

from ts-cacheable.

angelnikolov avatar angelnikolov commented on June 25, 2024

@jonstorer the issue you are experiencing is due to the fact that the decorators are executed immediately, once your class is parsed. Therefore, you will really have only one cache storage, which will be for your Repository class.
In short, each method is cached by combining the class constructor name + the property key (method name), unless you pass cacheKey as a custom config.
Obviously none of those options work for you, so there are two solutions to your problem.

Option 1

Use a decorator mixin (I have used that in a project of mine and it works well)

import {PCacheable, PCacheBuster} from 'ngx-cacheable';
import {Subject} from 'rxjs';
import {Duration} from '@app/enums/duration.enum';
/**
 * A base mixin decorator which will cache to our default service methods like getById, query and etc
 */
export function BaseApiCache(
  serviceName: string,
  cacheDuration: number = Duration.FIVE_MINUTES,
  cacheBusterSubject: Subject<any> = new Subject(),
  excludeMethods: string[] = []
): Function {
  /**
   * A mixin function which will take all properties of BaseApiCache and stamp them
   * on the provided constructor (i.e all services which require the basic get/set/update functionality)
   */
  return function (derivedCtor: Function): void {
    class BaseApiCache extends Api {
      cacheBuster = cacheBusterSubject;
      @PCacheable({
        cacheKey: serviceName + '#getById',
        maxAge: cacheDuration,
        cacheBusterObserver: cacheBusterSubject.asObservable()
      })
      public getById<TParams, TData>(
        id: string,
        asset?: string,
        params?: TParams,
        subEntities?: string
      ): Promise<TData> {
        return super.getById(id, asset, params, subEntities);
      }

      @PCacheable({
        cacheKey: serviceName + '#query',
        maxCacheCount: 5,
        maxAge: cacheDuration,
        cacheBusterObserver: cacheBusterSubject.asObservable()
      })
      public query<TParams, TData>(params?: TParams, asset?: string, subEntities?: string): Promise<TData> {
        return super.query<TParams, TData>(params, asset, subEntities);
      }

      @PCacheable({
        cacheKey: serviceName + '#count',
        maxAge: cacheDuration,
        cacheBusterObserver: cacheBusterSubject.asObservable()
      })
      public count(
        params: any
      ): Promise<number> {
        return super.count(params);
      }

      @PCacheBuster({
        cacheBusterNotifier: cacheBusterSubject
      })
      create<TData, TResponse>(data: TData, asset?: string): Promise<TResponse> {
        return super.create<TData, TResponse>(data, asset);
      }

      @PCacheBuster({
        cacheBusterNotifier: cacheBusterSubject
      })
      public upsert<TData, TResponse>(
        id: string,
        data: TData,
        asset?: string,
        isMultipart?: boolean,
        subEntities?: string
      ): Promise<TResponse> {
        return super.upsert<TData, TResponse>(id, data, asset, isMultipart, subEntities);
      }

      @PCacheBuster({
        cacheBusterNotifier: cacheBusterSubject
      })
      public update<TData, TResponse>(id: string, data: Partial<TData>, asset?: string, subEntities?: string): Promise<TResponse> {
        return super.update<TData, TResponse>(id, data, asset, subEntities);
      }

      @PCacheBuster({
        cacheBusterNotifier: cacheBusterSubject
      })
      public remove(id: string, asset?: string, subEntities?: string): Promise<void> {
        return super.remove(id, asset, subEntities);
      }
    }
    const fieldCollector = {};
    (BaseApiCache as Function).apply(fieldCollector);
    Object.getOwnPropertyNames(fieldCollector).forEach((name) => {
      derivedCtor.prototype[name] = fieldCollector[name];
    });

    Object.getOwnPropertyNames(BaseApiCache.prototype).forEach((name) => {
      if (name !== 'constructor' && excludeMethods.indexOf(name) === -1) {
        derivedCtor.prototype[name] = BaseApiCache.prototype[name];
      }
    });
  };
}

And then use it like:

/**
 * Make API calls to Parts endpoint.
 */
@BaseApiCache('partService', 1000000)
@Injectable()
export class PartService {
}

Option 2

I can add a cache config callback option of something like:
cacheKeyCallback which you can add to each Repository class method and will be called everytime, that method is called. In it, you can derive a unique cache key based on the service which the method is called from. So you can constructor a new key, which will essentially be somethign like PartService#find.
What do you think about that?

from ts-cacheable.

angelnikolov avatar angelnikolov commented on June 25, 2024

Scratch that, option 2 is not really an option since there will be no way to subscribe to cache busters.

from ts-cacheable.

jonstorer avatar jonstorer commented on June 25, 2024

@angelnikolov is there anyway to reach into the method and pay attention to the route being called? that could be used as the cache key.

from ts-cacheable.

angelnikolov avatar angelnikolov commented on June 25, 2024

Well, it actually is looking into the route being called.
Let's say you have this Repository service method:


  @Cacheable({
    cacheBusterObserver: cacheBuster$
  })
  public findById(id: string): Observable<T> {
    return this.http.get<T>(this.url(id));
  }

Then you call it through the WidgetService child by this.widgetService.findById('1').
This will essentially always go through the same Repository service method through the prototype chain. Therefore, there will only be 1 cache storage. For every different service call, it will remove the previous cache and store a new one.
Say you first call the widgetService.findById and then call partsService.findById. The produced url will be different, so the cache for the widgetService call will be evicted by the call of the parstService's method.
The way to resolve that is either to use my proposal here or increate the maxCacheCount of the base Repository class to a larger number, so all your method calls are cached.

from ts-cacheable.

jonstorer avatar jonstorer commented on June 25, 2024

in that example, the paths resolve to /widgets/1 and /parts/1. Does that make a difference? Based on what you're saying, the cache keys would be different.

edit: they actually resolve to full urls. https://api.example.com/widgets/1 & https://api.example.com/parts/1

from ts-cacheable.

angelnikolov avatar angelnikolov commented on June 25, 2024

@jonstorer Yes, it would be different, but since you are using a single service (Base repository service) and a single method (findById), all your calls will go to the same cache storage and the second call's cache will override the first one's.
If you add maxCacheCount: 100 you will store a hundred of responses in the cache of RepositoryService#findById. The other option is to use a mixin as described above, which will apply the decorator dynamically, to each service you want. Then you won't have this issue.

from ts-cacheable.

angelnikolov avatar angelnikolov commented on June 25, 2024

@jonstorer Anything to add here?

from ts-cacheable.

jonstorer avatar jonstorer commented on June 25, 2024

@angelnikolov I think there's an opportunity to identify your cache key further down in execution. I've not spent the proper time to research this, however, you have access to oldMethod which should be the specific method on the specific subclass. Again, I've not done my research, but won't that evaluate to the correct class name and method name?

`${oldMethod.constructor.name}/${oldMethod.name}

I will investigate this more. However, it won't be until next week sometime as I'm slammed this week

from ts-cacheable.

stale avatar stale commented on June 25, 2024

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

from ts-cacheable.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.