Skip to content

Instantly share code, notes, and snippets.

@jgeewax
Last active October 26, 2015 18:58
Show Gist options
  • Save jgeewax/3b351d3522fdf061bdea to your computer and use it in GitHub Desktop.
Save jgeewax/3b351d3522fdf061bdea to your computer and use it in GitHub Desktop.
var express = require('express');
var app = express();
var gcloud = require('gcloud')({ /* extra config here */ });
var dataset = gcloud.datastore.dataset({ /* projectId: my-project */ });
// Set the trace ID on a per-request basis via middleware
// (See http://expressjs.com/guide/writing-middleware.html)
app.use(function(req, res, next) {
// Need to double check that this works from a thread safety perspective.
gcloud.setTraceContext(req.headers['X-Cloud-Trace-Context']);
});
// Get companies from the datastore.
app.get('/company/:id', function(req, res, next) {
var key = dataset.Key(['Company', req.params.id]);
// This request will have the trace ID that was set by the middleware.
// So whatever was in the request header will be automatically forwarded along.
dataset.get(key).on('data', function(entity) {
res.send(entity);
});
});
@stephenplusplus
Copy link

Proposal:

var express = require('express');
var app = express();

var gcloud = require('gcloud')({ /* extra config here */ });

// Set the trace ID on a per-request basis via middleware
// (See http://expressjs.com/guide/writing-middleware.html)
app.use(function(req, res, next) {
  res.locals.dataset = gcloud.datastore.dataset({
    projectId: 'my-project',
    decorateRequest: function(reqOpts) {
      reqOpts.headers['X-Cloud-Trace-Context'] = req.headers['X-Cloud-Trace-Context'];
      return reqOpts;
    }
  });

  next();
});

// Get companies from the datastore.
app.get('/company/:id', function(req, res, next) {
  var dataset = res.locals.dataset;
  var key = dataset.Key(['Company', req.params.id]);
  // This request will have the trace ID that was set by the middleware.
  // So whatever was in the request header will be automatically forwarded along.
  dataset.get(key).on('data', function(entity) {
    res.send(entity);
  });
});

@jgeewax
Copy link
Author

jgeewax commented Oct 26, 2015

Can you configure a dataset/gcloud more than once? Effectively "forking" it? If so...

var express = require('express');
var app = express();

var gcloud = require('gcloud')({ /* extra config here */ });
var dataset = gcloud.datastore.dataset({projectId: 'my-project'});

// Set the trace ID on a per-request basis via middleware
// (See http://expressjs.com/guide/writing-middleware.html)
app.use(function(req, res, next) {
  res.locals.dataset = dataset({
    decorateRequest: function(reqOpts) {
      reqOpts.headers['X-Cloud-Trace-Context'] = req.headers['X-Cloud-Trace-Context'];
      return reqOpts;
    }
  });

  next();
});

// Get companies from the datastore.
app.get('/company/:id', function(req, res, next) {
  var dataset = res.locals.dataset;
  var key = dataset.Key(['Company', req.params.id]);
  // This request will have the trace ID that was set by the middleware.
  // So whatever was in the request header will be automatically forwarded along.
  dataset.get(key).on('data', function(entity) {
    res.send(entity);
  });
});

@jgeewax
Copy link
Author

jgeewax commented Oct 26, 2015

And this opens the question (@stephenplusplus), can we change the parent only and have the decorator inherit down somehow...? ie...

var gcloud = require('gcloud')(...);
var dataset = gcloud.datastore.dataset(...);

var decorator = function() {};
gcloud.magicallySetDecorator(decorator);
assert.equals(dataset.getDecorator(), decorator);

@stephenplusplus
Copy link

Each invocation of dataset() returns a fresh auth client. So having to create one on each GET means you'd also end up doing a fresh token grab every time. But without scoping request option manipulation to the object, you'd inevitably run into concurrency issues, so it might be a fair trade off for what is not the 99% use case?

If not, we can go the forking route, but it'd have to be from a new method (like dataset.clone()) that would reuse/override options.

var dataset = gcloud.datastore.dataset({ projectId: 'my-project' });

app.use(function(req, res, next) {
  res.locals.dataset = dataset.clone({
    decorateRequest: function(reqOpts) {
      reqOpts.headers['X-Cloud-Trace-Context'] = req.headers['X-Cloud-Trace-Context'];
      return reqOpts;
    }
  });

  next();
});

And an option that is less "it's as easy as 1-2-3" for the user, but possibly easier to grasp / re-usable and expandable down the road:

var opts = { projectId: 'my-project-id' };
var dataset = gcloud.datastore.dataset(opts);

app.use(function(req, res, next) {
  res.locals.dataset = gcloud.datastore.dataset(extend(opts, {
    authClient: dataset.authClient, // expose and re-use the authClient
    decorateRequest: function(reqOpts) {
      reqOpts.headers['X-Cloud-Trace-Context'] = req.headers['X-Cloud-Trace-Context'];
    }
  }));

  next();
});

There are many ways to implement the above concept with/without using extend, using helper functions, etc, so if it doesn't suit your taste in terms of style, try to look past :)

@stephenplusplus
Copy link

And this opens the question (@stephenplusplus), can we change the parent only and have the decorator inherit down somehow...? ie...

Man, gists really need email notifications or something. I'm always one step behind.

But yeah, I would expect the user to expect an inheritance model, where anything set on the highest level cascades downwards, and the lowest object wins in the event of a conflict:

var gcloud = require('gcloud')({
  decorateRequest: function(reqOpts) {
    reqOpts.headers['A'] = 1;
    reqOpts.headers['B'] = 1;
    return reqOpts;
  }
});

var bucket = gcloud.bucket({
  decorateRequest: function(reqOpts) {
    reqOpts.headers['A'] = 2;
    return reqOpts;
  }
});

bucket.getMetadata();
// GET https://...
// A: 2
// B: 1

@jgeewax
Copy link
Author

jgeewax commented Oct 26, 2015

Sure, but the example we're talking about is...

var gcloud = require('gcloud');
var bucket = gcloud.bucket();

var threadLocalgcloud = gcloud({
  decorateRequest: function(reqOpts) {
    reqOpts.headers['A'] = 1;
    return reqOpts;
  }
});

bucket.getMetadata();
// GET https://...
// A: 1

That is, I mess with the parent somehow, and the pre-created child gets updated... It seems like that wouldn't be possible to me.... right?

@stephenplusplus
Copy link

In that example, bucket.getMetadata() would not know anything about the decorated request options. You would have to access the bucket from threadLocalgcloud.bucket() to have that stuff on there.

In a practical use, if I need to decorate all requests, I would do this:

var gcloud = require('gcloud')({
  decorateRequest: function(reqOpts) {
    reqOpts.headers['A'] = 1;
    reqOpts.headers['B'] = 1;
    return reqOpts;
  }
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment