Meteor中的延迟状态检查

时间:2015-02-02 18:06:02

标签: javascript mongodb design-patterns meteor

延迟状态检查

tl; dr - 在指定的持续时间内没有采取行动时,有什么好的设计模式可以作出反应?


问题设置

我正在制作类似Farmville的应用程序。在这个应用程序中,用户有他们照顾的花园。每个花园都会跟踪一些变量 - 湿度,温度,pH值,硝酸盐等。应用程序将引导您完成照顾花园,为您创建任务。根据您提交的读数,它会通知您这些变量是否过低或过高。此外,如果你没有在一段特定的时间内读取变量,它会提醒你读取变量。

园区数据看起来像

// garden object
{
  name: "Home garden",
  variables: {
    nitrate: {
      currentValue: 1.7,
      lastMeasuredAt: // some Date
    },
    nitrite: {
      currentValue: 0.5,
      lastMeasuredAt: // some Date
    }
  }
}

因此,假设有一项名为“添加肥料”的任务。该任务的触发因素是硝酸盐低于2.5ppm的情况。假设任务数据看起来像

// task object
{
  name: "Add fertilizer",
  instructions: "Open bag, dump on garden",
  condition: {
    variable: "nitrate",
    operator: "$lt",
    threshold: 2.5
  }
}

我使用condition数据构建搜索查询,并查询Gardens以查看是否符合条件。我正在使用MongoDB,因此可以使用普通的旧Javascript对象和Meteor构建该查询,因此我有一个实时更新游标。

query = { variables.nitrate.currentValue: { $lt : 2.5 }};
Gardens.find(query).observe({
  added: function( garden ) {
    // add the task to the garden's task list
  },
  removed: function( garden ) {
    // remove the task from the garden's task list
  }
});

问题

好消息,这种模式适用于我描述的任务。但是,当任务基于已经过去的持续时间时呢?

// time-based task object
{
  name: "Take a nitrate reading",
  instructions: "Insert the nitrate probe",
  condition: {
    variable: "nitrate",
    operator: "$lt",
    interval: 2880 // 2 days in minutes
  }
}

我发现这是一个时间任务,因为它有一个interval而不是threshold并且可以进行查询......

// using moment.js
expiration = moment().subtract(interval, 'minutes').toDate();
// find when lastMeasuredAt + interval < Now
query = { variables.nitrate.lastMeasuredAt: { $gt: expiration }};
Gardens.find(query).observe({
  added: function( garden ) {
    // add the task to the garden's task list
  },
  removed: function( garden ) {
    // remove the task from the garden's task list
  }
});

但是这需要在将来的某个时刻进行检查,而不是现在。这让我想到了问题的关键。什么是检查何时到期的好方法?

问题

有没有办法反应这样做?

Meteor的Tracker仅支持在客户端上运行,但peerlibrary:server-autorun确实在服务器上启用了它。我可以使Date成为一个被动数据源,每隔几分钟更新一次,并将其包装在Tracker.autorun中。

这通常是用某种作业队列处理的吗?

如果使用作业队列实现,当将来提交一个推送到期日期的新读数时,更新现有作业或删除旧作业并创建新作业会更好吗?

1 个答案:

答案 0 :(得分:2)

有没有办法反应这样做?

这通常是通过某种作业队列来处理的吗?

我最推荐的模式是定期检查“过期”事件的某种作业队列。

至于反应性 - 它在每种情况下都不能很好地发挥作用。基于计时器的依赖性失效有更高效的结构。

以下代码可用于为基于响应的计时器系统,甚至是收集支持的计时器系统供电。

如果您觉得这个答案没有达到标准,请发表评论,以便我可以对其进行改进。

// executes it's tasks regularly.
// up to the tasks system to implement "enqueue" & "execute"
ScheduledExecutor = (function() {

  function ScheduledExecutor(tasks, checkInterval) {
    this.tasks = tasks;
    this.checkInterval = checkInterval;
    this.running = false;
    this.timer = null;
    this.tick = _.bind(this.tick, this);
  }

  _.extend(ScheduledExecutor.prototype, {
    enqueue: function(task){
      this.tasks.enqueue(task);
    },
    tick: function(){
      this.tasks.execute();
      if (this.running) {
        this.timer = Meteor.setTimeout(this, this.checkInterval);
      }
    },
    start: function(){
      if (!this.running){
        this.running = true;
        this.tick();
      }
    },
    stop: function(){
      this.running = false;
      Meteor.clearTimeout(this.timer);
      this.timer = null;
    }
  });
  return ScheduledExecutor;
})();

// example of "reactive" task list.
//  finds due dependencies, and invalidates them
ReactiveTasks = (function(){
  function ReactiveTasks(){
    this.taskList = [];
  }

  _.extend(ReactiveTasks.prototype, {
    execute: function(){
      var now = Date.now();
      _.findWhere(this.taskList, function(task){
        return task.due <= now;
      }).forEach(function(task){
        task.dep.changed()
      });
      this.taskList = _.filter(this.taskList, function(task){
        return task.due > now;
      });
    },
    enqueue: function(due){
      var dep = new Tracker.Dependency;
      dep.depend();
      this.taskList.push({
        due: due,
        dep: dep
      });
    }
  });
  return ReactiveTasks;
})();

// executes tasks backed by a collection
//  finds "due" tasks, then calls the "executor"
//  the executor should interpret the task, and 
/// call the correct library function
CollectionTasks = (function(){
  function CollectionTasks(collection, executor){
    this.collection = collection;
    this.executor = executor;
  }
  _.extend(CollectionTasks.prototype, {
    execute: function(){
      var self = this, 
        now = Date.now();
      self.collection.find({
        due: {
          $lte: now
        }
      }).forEach(function(task){
        self.collection.remove({_id: task._id});
        self.executor.execute(task);
      });
    },
    enqueue: function(task){
      this.collection.insert(task);
    }
  });
  return CollectionTasks;
})();