(著者:サイボウズ KADOYA Ryo)
「入出庫」アプリには、入庫処理、出庫処理の情報が登録されます。
起こりうる問題
ここで起こりうる問題は以下のふたつです。
在庫数の増減時に競合が発生する
たとえば、Aという商品が10個あったとします。Aに対して、山田さんが5個の入庫を行い、鈴木さんが8個の出庫を行います。
Aの操作 | 増減 | 在庫数 |
---|---|---|
初期値 | 10 | |
入庫 | 5 | 15 |
出庫 | -8 | 7 |
その結果、期待されるAの在庫数は、7個です。ところが、以下のように山田さんと鈴木さんが処理を同時に行ったケースを考えてみます。
- 山田さんが在庫数を確認する。→10個
- 鈴木さんが在庫数を確認する。→10個
- 山田さんが在庫数を5増やす→15個
- 鈴木さんが在庫数を8減らす→2個
山田さんが5増やしているにも関わらず、最終的な在庫数が2になってしまいます。RDBMSでは、これを回避するために、SELECT FOR UPDATEを用いて排他制御を行うのが一般的です。
処理実行のロールバックができない
入出庫の処理を行う場合、以下のふたつの操作を行うことになります。
- 「入出庫」アプリに登録する
- 「在庫」アプリの在庫数を更新する
上のふたつの操作が片方だけ実行されてしまった場合、在庫の状態に矛盾が生じてしまいます。RDBMSでは、トランザクションを利用して、矛盾がないことを保証することができます。
ここでは、kintoneでこれらの問題を回避するテクニックを紹介します。
在庫数を取得する
GET /k/v1/record.json?app={在庫アプリのID}&id=1 HTTP/1.1 Host: {sub-domain}.cybozu.com:443
{
"record":{
"record_id": {
"type": "RECORD_NUMBER",
"value": "1"
},
"$revision":{
"type":"__REVISION__",
"value":"17"
},
"在庫数":{
"type":"NUMBER",
"value":"10"
}
}
}
$revision というフィールドが取得されていることがわかります。このフィールドは、取得したレコードの、現在のリビジョン番号を返します。この値を使用することで、レコードが他のユーザーによって更新されたかどうかを検知することができます。
入出庫アプリへの登録と、在庫数の更新を同時に行う
bulkRequest APIを使うと、複数のアプリへの処理を一括で行うことができます。いずれかの処理が失敗した場合、すべてのリクエストがロールバックされます。
POST /k/v1/bulkRequest.json HTTP/1.1 Host: {sub-domain}.cybozu.com:443
{
"requests": [
{
"method": "PUT",
"api": "/k/v1/record.json",
"payload": {
"app": {在庫アプリのID},
"id": 1,
"revision": 17,
"record": {
"在庫数": {
"value": "15"
}
}
}
},
{
"method": "POST",
"api": "/k/v1/record.json",
"payload": {
"app": {入出庫アプリのID},
"record": {
"入庫数": {
"value": "5"
}
}
}
}
]
}
このとき、在庫アプリの更新データに対して、revisionパラメーターを指定していることに注目してください。revisionパラメーターには、在庫数を取得したときの$revisionフィールドと同じ値を設定します。リクエストの処理時に、他のユーザーによって変更があった場合には、リビジョン番号の不一致が発生し、更新処理をキャンセルして409 Conflictエラーが返ります。
これを楽観的並行性制御と呼びます。
revisionによる並行性制御とbulkRequest APIを使うことによって、確実な入出庫処理を行うことができます。
ぜひお試しください。
このTipsは、2014年4月版で確認したものになります。
Javascript API で同じ処理はできませんか?
株式会社オオヤさん
cybozu.com developer network事務局です。
Javascript API でREST APIを使えば同様にできるかと思います。