需要在 DynamoDB 的某個 table 的 Hash Key 是用 auto increment 的方式儲存 id,不過如果只單靠一張 table 有點難完成這樣的機制,所以需要再生另一張 table 用來存放目前的值,讓原本的 table 可以去參考以取得新增後的數值回來儲存。
可以參考這篇:How to make a UUID in DynamoDB?,做法大概就是像這樣:
Each time you want to generate a new id, you would do the following:
Do a GetItem on NextIdTable to read the current value of Counter → curValue
Do a PutItem on NextIdTable to set the value of Counter to curValue + 1. Make this a conditional > PutItem so that it will fail if the value of Counter has changed.
If that conditional PutItem failed, it means someone else was doing this at the same time as you were. Start over.
If it succeeded, then curValue is your new unique ID.
為了日後的彈性,所以做了一張叫 NextId
table,裡面的資料格式長這樣:
{
"NextKey": "user",
"NextId": 0
}
其中 NextKey 是 hash key 這樣未來方便做不同資料類型可以共用 NextId table 來儲存/產生 專屬的數值 NextId。
然後因為專案是用 LoopBack 的架構,所以 NextId model 會做一個 API 讓其它 model 可以一次完成取得最新的 NextId。做法其實就是叫用 DynamoDB 的 Conditional Update:UpdateItem - Amazon DynamoDB
如果是用 dynogels 這個套件的話,事情會比較簡單,把 expressions 準備好後再傳進去:
NextId.update(
{ NextKey: data_type },
"SET #nextId=:nextId",
"#nextId = :current",
{ ":nextId": current + 1, ":current": data_current },
{ "#nextId": "NextId" },
(err, result) => {
if (err) {
winston.error("=== Update NextId (%s) failed: %j", data_type, err);
return done(err);
}
return done(null, result);
}
);
然後回到原本的 table,接下來是要把這個 model 在每次新增資料時能夠將新的 nextId 塞入,而 LoopBack 有提供 model 操作時的 各項 hook 串接,方便我們可以在儲存資料先做些前處理。
只要在這個 model 裡補上 before save 的這個 hook 定義即可,對應的實作則是呼叫剛剛的 NextId model 以取得新的 nextId 值回來,再修補即將要存入的 model instance,實作如下:
const DATA_TYPE = "user";
module.exports = (UserNew) => {
UserNew.observe("before save", (ctx, next) => {
if (!ctx.instance) {
return next();
}
let user = ctx.instance;
UserNew.app.models.NextId.getNextId(DATA_TYPE, (err, result) => {
if (err) {
winston.error("=== Calling nextId failed: %j", err);
return next();
}
user.Id = result.get("NextId") + "";
next();
});
});
};
最後提一下這個方式的缺點,即是無法保證每次的 nextId 的值是連續性的,因為在每次取得 NextId model 後,後續的儲存主體資料可能是失敗或中斷;雖然如此,但只要能滿足每次的 NextId 值是 unique 這個基本需求我想就足夠應付了。