Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where to store my node-schedules

I'm very new to Node/Express and I'm making an appointment system. I want my users to make an appointment for the day they want and my system sends them notification on that exact time. I found "node-schedule" module which is really good for this task but I don't know where to implemented this. Is there anyway to store all tasks in my app.js or is it enough to just create a node-schedule task every time I hit certain end point for example:

router.get('/', function(req, res, next) {
 var j = schedule.scheduleJob(date, function(){
   send notification();
});
res.send(200);
}

Note: I do not want to run a constant for loop on my sql table to check for dates

like image 450
Hirad Roshandel Avatar asked Jun 03 '15 21:06

Hirad Roshandel


2 Answers

You'll need to persist your application data to some form of permanent storage by either writing to local files using something like SQLite, running your own database server (like MongoDB) or using a cloud-based storage service like Amazon SimpleDb.

Each of these options (and many others) have npm modules you can use to read/write/delete persistent data. For examples, see MongoDb, SQLite3, and SimpleDb, all available using npm on npmjs.com.

UPDATE

Per your comment below: Well, you did ask where you could store your scheduled events. ;)

To persist all scheduled events so they survive possible server failure, you'd need to create a storable data structure to represent them and for each event, create a new instance of your representation and store it to your persistent storage (MySQL).

Typically, you'd use something along the lines of:

{
  when:DateTime        -- timestamp when the event should fire
  what:Action          -- what this event should do
  args:Arguments       -- arguments to pass to Action
  pending:Boolean=true -- if false, this event has already fired
}

When you initialize your server, you would query your persistent storage for all events where pending===true and using the results, initialize instances of the node-schedule module.

When you need to schedule a new event while your server was running, you'd create a new event representation, write it into persistent storage and create a new instance of node-schedule using it.

Finally, and most importantly for customer happiness, when a scheduled event completes successfully, just before your event handler (the Action mentioned above) completes, it would need to mark the persistent version of the event it is handling as pending:false so you don't fire any event more than once.

So for example:

  'use strict';
  
  var scheduler = require('node-schedule');

  /**
   * Storable Representation of a Scheduled Event
   *
   * @param {string|Date} when
   * @param {string} what
   * @param {array.<string>} [args=[]]
   * @param {boolean} [pending=true]
   *
   * @property {Date} PersistentEvent.when       - the datetime this event should fire.
   * @property {string} PersistentEvent.what     - the name of the action to run (must match key of PersistentEvent.Actions)
   * @property {array} PersistentEvent.args      - args to pass to action event handler.
   * @property {boolean} PersistentEvent.pending - if true, this event has not yet fired.
   *
   * @constructor
   *
   * @example
   *
   * var PersistentEvent = require('PersistentEvent'),
   *     mysql = require('mysql'),
   *     conn = mysql.createConnection({ ... });
   *
   * conn.connect();
   *
   * // at some point when initializing your app...
   *
   * // assign your persistent storage connection...
   * PersistentEvent.setStore(conn);
   *
   * // load all pending event from persistent storage...
   * PersistentEvent.loadAll$(function (err) {
   *   if (err) {
   *     throw new Error('failed to load all PersistentEvents: ' + err);
   *   }
   *
   *   // from this point on, all persistent events are loaded and running.
   *
   * });
   */
  var PersistentEvent = function (when, what, args, pending) {
    // initialize
    PersistentEvent.Cache.push(this.init({
      when: when,
      what: what,
      args: args,
      pending: pending
    }));
  };
  
  // ==== PersistentEvent Static Methods ====
  
  /**
   * Pre-defined action event handlers.
   * <p>
   * Where the property key will be used to match the PersistentEvent.what property,
   * and the property value is a event handler function that accepts an optional
   * array of args and a callback (provided by PersistentEvent.prototype.schedule)
   * </p>
   *
   * @property {object}
   * @property {function} Actions.doSomething
   * @property {function} Actions.doSomethingElse
   *
   * @static
   */
  PersistentEvent.Actions = {
    doSomething: function (args, cb) {
      // defaults
      args = args || [];
  
      // TODO check specific args here ...
  
      var result = true,
          err = null;
  
      // do your action here, possibly with passed args
  
      cb(err, result);
    },
    doSomethingElse: function (args, cb) {
      // defaults
      args = args || [];
  
      // TODO check specific args here ...
  
      var result = true,
          err = null;
  
      // do your action here, possibly with passed args
  
      cb(err, result);
    }
  };
  
  /**
   * Cache of all PersistentEvents
   *
   * @type {Array.<PersistentEvent>}
   * @static
   */
  PersistentEvent.Cache = [];
  
  // Data Management
  
  /**
   * Connection to persistent storage.
   * TODO - This should be abstracted to handle other engines that MySQL.
   * @property {object}
   * @static
   */
  PersistentEvent.StorageConnection = null;
  
  /**
   * Sets the storage connection used to persist events.
   *
   * @param {object} storageConnection
   * @static
   */
  PersistentEvent.setStore = function (storageConnection) { // set the persistent storage connection
                                                            // TODO - check args here...
  
    // Note: this function isn't really needed unless you're using other kinds of storage engines
    // where you'd want to test what engine was used and mutate this interface accordingly.
  
    PersistentEvent.StorageConnection = storageConnection;
  };
  
  /**
   * Saves a PersistentEvent to StorageConnection.
   *
   * @param {PersistentEvent} event - event to save
   * @param {function} cb - callback on complete
   * @static
   */
  PersistentEvent.save$ = function (event, cb) {
    var conn = PersistentEvent.StorageConnection;
  
    if (null === conn) {
      throw new Error('requires a StorageConnection');
    }
  
    // TODO - check for active connection here...
  
    // TODO - check args here...
  
    conn.query('INSERT INTO TABLE when = :when, what = :what, args = :args, pending = :pending', event, cb);
  };
  
  /**
   * Loads all PersistentEvents from StorageConnection.
   * @param {function} cb -- callback on complete
   * @static
   */
  PersistentEvent.loadAll$ = function (cb) {
    var conn = PersistentEvent.StorageConnection;
  
    if (null === conn) {
      throw new Error('requires a StorageConnection');
    }
  
    // check for active connection here...
  
    // check args here...
  
    conn.query('QUERY * FROM TABLE WHERE pending = true', function (err, results) {
      if (err) {
        return cb(err);
      }
      results.forEach(function (result) {
        // TODO: check for existence of required fields here...
        var event = new PersistentEvent(result.when, result.what, result.args, true);
        event.schedule();
      });
      cb(null);
    });
  };
  
  // ==== PersistentEvent Methods ====
  
  /**
   * Initialize an instance of PersistentEvent.
   *
   * @param {object} opts
   * @return {PersistentEvent}
   */
  Event.prototype.init = function (opts) {
    // check args
    if ('object' !== typeof opts) {
      throw new Error('opts must be an object');
    }
  
    // set defaults
    opts.args = opts.args || [];
    opts.pending = opts.pending || true;
  
    // convert string to Date, if required
    if ('string' === typeof opts.when) {
      opts.when = new Date(opts.when);
    }
  
    // check that opts contains needed properties
    if (!opts.when instanceof Date) {
      throw new Error('when must be a string representation of a Date or a Date object');
    }
  
    if ('string' !== typeof opts.what) {
      throw new Error('what must be a string containing an action name');
    }
  
    if (!Array.isArray(opts.args)) {
      throw new Error('args must be an array');
    }
  
    if ('boolean' !== typeof opts.pending) {
      throw new Error('pending must be a boolean');
    }
  
    // set our properties
    var self = this;
    Object.keys(opts).forEach(function (key) {
      if (opts.hasOwnProperty(key)) {
        self = opts[key];
      }
    });
  
    return this;
  };
  
  /**
   * Override for Object.toString()
   * @returns {string}
   */
  PersistentEvent.prototype.toString = function () {
    return JSON.stringify(this);
  };
  
  /**
   * Schedule the event to run.<br/>
   * <em>Side-effect: saves event to persistent storage.</em>
   */
  PersistentEvent.prototype.schedule = function () {
    var self = this,
        handler = Actions[this.what];
  
    if ('function' !== typeof handler) {
      throw new Error('no handler found for action:' + this.what);
    }
  
    PersistentEvent.save$(self, function () {
      self._event = scheduler.scheduleJob(self.when, function () {
        handler(self.args, function (err, result) {
          if (err) {
            console.error('event ' + self + ' failed:' + err);
          }
          self.setComplete();
        });
  
      });
    });
  };
  
  /**
   * Sets this event complete.<br/>
   * <em>Side-effect: saves event to persistent storage.</em>
   */
  PersistentEvent.prototype.setComplete = function () {
    var self = this;
    delete this._event;
    this.pending = false;
    PersistentEvent.save$(this, function (err) {
      if (err) {
        console.error('failed to save event ' + self + ' :' + err);
      }
    });
  };

Note that is a first-pass boilerplate to show you one way of designing a solution to your problem. It will require further effort on your part to run.

like image 63
Rob Raisch Avatar answered Oct 18 '22 06:10

Rob Raisch


You can have a cron job every morning that will pick all the appointments for that day and schedule pushes for them. This way you'll have to query the DB once that too on the time when the server load is minimal.

like image 3
Aditya_Anand Avatar answered Oct 18 '22 06:10

Aditya_Anand