Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: Provide access to shared cache in routes #153

Open
ekosz opened this issue Dec 6, 2015 · 4 comments
Open

Feature request: Provide access to shared cache in routes #153

ekosz opened this issue Dec 6, 2015 · 4 comments

Comments

@ekosz
Copy link

ekosz commented Dec 6, 2015

For the past couple months I've been developing a medium sized application based on falcor. I've noticed a pattern emerge in my route files. Each route must perform some authentication to make sure the current user token has access to the data the route provides. That means nearly each route must make a request to the user service asking information about the user. Some requests to the router may kick off the same request each time.

The faclor router is already caching the results of each route, it would be very helpful if that cache (or some other one) was provided to the route. Here's what I'm imagining:

Code today:

[{
  route: 'my',
  get: function(pathSet) { return { $type: 'ref': value: ['usersById', this.userId] }; },
}, {
  route: 'usersById[{keys:userIds}]["permission_level"]',
  get: function(pathSet) { /* get permission level from user service */ },
}, {
  route: 'usersById[{keys:userIds}]["secret_info"]',
  get: function(pathSet) {
    return userService.getInfo(this.userId).then(user => {
      if (user.permission_level !== 'admin') throw new Error('access denied');
      // Continue on
    });
  },
}]

After exposing the cache:

[{
  route: 'my',
  get: function(pathSet) { return { $type: 'ref': value: ['usersById', this.userId] }; },
}, {
  route: 'usersById[{keys:userIds}]["permission_level"]',
  get: function(pathSet, cache) { /* get permission level from user service */ },
}, {
  route: 'usersById[{keys:userIds}]["secret_info"]',
  get: function(pathSet, cache) {
    return cache.getValue(['my', 'permission_level']).then(permission_level => {
      if (permission_level !== 'admin') throw new Error('access denied');
      // Continue on
    });
  },
}]

This way when multiple routes need the current user's permission level they can share the cached value rather than reaching out the user service each time. Does this make sense?

@ThePrimeagen
Copy link
Contributor

What you are asking for can be easily accommodated with using a falcor model a user permissions router.

var userPermissionsRouter = new Router([{
    route: 'usersById[{keys:userIds}].permission_level',
    get: function getUsersSecretInfo(pathSet) {
        // returns the pathValues / jsonGraph of users premissions.
    }
}]);
var usersModel = new Model({
    source: userPermissionsRouter
});


// In your router somewhere
[{
  route: 'my',
  get: function(pathSet) { return { $type: 'ref': value: ['usersById', this.userId] }; },
}, {
  route: 'usersById[{keys:userIds}]["permission_level"]',
  get: function(pathSet) { /* get permission level from user service */ },
}, {
  route: 'usersById[{keys:userIds}]["secret_info"]',
  get: function(pathSet) {
    // Technically you can get many users so I would not use `getValue`
    return usersModel.getValue(['usersById', pathSet[1][0], 'permission_level']).then(permission_level => {
      if (permission_level !== 'admin') throw new Error('access denied');
      // Continue on
    });
  },
}]

Thoughts? Its essentially using a model to cache / batch requests for concurrent and sequential accessing to specific parts of your services.

@ekosz
Copy link
Author

ekosz commented Dec 6, 2015

Oh interesting. So basicly reinitialize the existing router as a falcor model and pass it through?

app.use('/model.json', falcorMiddleware.dataSourceRoute((req, res) => {
  const cache = new Model({source: new Router(req, res)});
  return new Router(req, res, cache);
}));

Then in my routes I can check if this.cache is available, if it is pull from there, otherwise grab the values from the original source.

Is this a known / used pattern?

@intellix
Copy link

intellix commented Dec 7, 2015

I suppose the inevitable question follows: is it possible to just use the same router/routes rather than creating a second instance?

var router = new Router([{
  route: 'my',
  get: function(pathSet) { return { $type: 'ref': value: ['usersById', this.userId] }; },
}, {
  route: 'usersById[{keys:userIds}]["permission_level"]',
  get: function getUsersSecretInfo(pathSet) {
    // returns the pathValues / jsonGraph of users premissions.
  }
}, {
  route: 'usersById[{keys:userIds}]["secret_info"]',
  get: function(pathSet) {
    return model.getValue(['usersById', pathSet[1][0], 'permission_level']).then(permission_level => {
      if (permission_level !== 'admin') throw new Error('access denied');
      // Continue on
    });
  },
}]);
var model = new Model({
  source: router
});

@greim
Copy link

greim commented Feb 11, 2016

Another option might be to use memoization. This is super-simplified, but hopefully shows the idea.

function getUserById(id) {
  // call the REST service
  // return a promise
}

var RouterBase = FalcorRouter.createClass([{
  route: "...",
  async get(pathSet) {
    // stuff
    var user = await this.getUserById(id);
    // stuff
  }
}]);

class Router extends RouterBase {
  constructor() {
    super();
    this.getUserById = _.memoize(getUserById);
  }
}

// in the server something like...
app.use('/model.json', falcorMiddleware(function() {
  return new Router();
});

The same user might be requested many times in the same request cycle, but the REST service only sees one hit. Each request handler gets a fresh memoization cache, so no worries about cross-process state inconsistencies, cache invalidation, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants