目前数据存储服务使用MongoDB 3.4,不具备处理事务的能力。您可以参考本文的示例对数据进行原子并发操作。

原子插入

要保证同一条信息不可插入两次,在集合上新增一个字段并且设置unique index,如下所示:

// 创建唯一索引
mpserverless.db.collection('order').createIndex(
  { 'orderId': 1 },
  { 'unique': true }
);

// 插入时验证
function placeOrder(items) {
  const orderId = uuid();
  mpserverless.db.collection('order').insertOne({
    orderId,
    items,
  }).then(res => {
    console.log('order placed successful');
  }).catch(err => {
    console.error('insert failed');
  });
}

原子更新

原子更新可以通过判断当前的最后更新时间来匹配是否可以更新。例如,在发送一个礼物时,A和B同时领取,在不支持原子更新的情况下,可能导致两个用户都抢到的情况。

mpserverless.db.collection('gifts').createIndex('inventory.itemId');

function giveOutGift(owner, item) {
  // 发礼物
  return mpserverless.db.collection('inventory').updateOne({
    user: owner,
  }, {
    $addToSet: { // 保证唯一性,subdocument 不能建立唯一索引
      inventory: {
        item,
        itemId: item.id,
        status: 'unclaimed',
      },
    },
  }, { upsert: true });
}

function freezeGift(item) {
  return mpserverless.db.collection('gifts').updateOne({
    inventory: {
      $elemMatch: { // 找到数组里对应的礼物
        itemId: item.id,
        status: 'unclaimed',
      },
    },
  }, {
    $set: {
      'inventory.$.status': { status: 'transferring' }, // 设置成转移中
    },
  }).then(res => {
    if (res.affectedDocs !== 1) {
      return Promise.reject();
    }
  });
}

function claimGift(to, item) {
  return mpserverless.db.collection('gifts').updateOne({
    user: to,
  }, {
    $addToSet: {
      inventory: { 
        item,
        itemId: item.id,
        status: 'owned'
      },
    },
  }, {
    upsert: true,
  }).then(res => {
    if (res.affectedDocs !== 1) {
      return Promise.reject();
    }
  });
}

function claimUserGift(from, to, item) {
  return freezeGift().then(claimGift).then(() => mpserverless.db.collection('gifts').updateOne({
    user: from,
    inventory: {
      $elemMatch: {
        itemId: item.id,
        status: 'transferring',
      },
    },
  }, {
    $set: {
      'inventory.$.status': 'claimed',
    },
  });
}

giveOutGift('alice', {
  name: 'umbrella',
  id: '5b9642e109d54b4c12d68c7e',
}).then(res => claimUserGift('alice', 'bob', {
  name: 'umbrella',
  id: '5b9642e109d54b4c12d68c7e',
});