バージョン管理ルール
MBC CQRS サーバーレスフレームワークは、バージョン番号を使用した楽観的ロックを実装し、分散システムでのデータ一貫性を確保します。このガイドでは、バージョン管理ルールを説明し、実装例を提供します。
基本ルール
- 
同一PK/SKのシーケンシャルバージョニング
- 同一のPK/SKを持つアイテムは、バージョンを1から順次設定する必要があります。
 - 各更新でバージョン番号が1増加します。
 - 指定されたバージョンで最初のリクエストのみが成功します。
 - 同じバージョンでの後続リクエストは競合エラーで失敗します。
 
 - 
独立したバージョンシーケンス
- 異なるPK/SKの組み合わせごとに、独自のバージョンシーケンスを1から開始します。
 - バージョンシーケンスは、各PK/SKの組み合わせごとに独立して管理されます。
 - これにより、異なるアイテム に対する並列操作がバージョン競合なしで可能になります。
 
 - 
楽観的ロック
- 同一アイテムへの同時更新を防止するために使用されます。
 - バージョン番号は各更新時に自動的にインクリメントされます。
 - バージョン競合時にConditionalCheckFailedExceptionをスローします。
 - 分散環境でのデータ一貫性を確保します。
 
 
実装例
基本的なバージョン管理
describe('Version Handling', () => {
  it('should handle sequential versions correctly', async () => {
    // バージョン0で初期作成
    const createPayload = {
      pk: 'TEST#VERSION',
      sk: 'item#1',
      id: 'TEST#VERSION#item#1',
      name: 'Version Test',
      version: 0,
      type: 'TEST',
    }
    const createRes = await request(config.apiBaseUrl)
      .post('/items')
      .send(createPayload)
    expect(createRes.statusCode).toBe(201)
    expect(createRes.body.version).toBe(1)
    // 正しいバージョンで更新
    const updatePayload = {
      ...createPayload,
      version: 1,
      name: 'Updated Name',
    }
    const updateRes = await request(config.apiBaseUrl)
      .put(`/items/${createPayload.id}`)
      .send(updatePayload)
    expect(updateRes.statusCode).toBe(200)
    expect(updateRes.body.version).toBe(2)
  })
})
バージョン競合の処理
describe('Version Conflicts', () => {
  it('should handle concurrent updates correctly', async () => {
    const payload = {
      pk: 'TEST#VERSION',
      sk: 'conflict#1',
      id: 'TEST#VERSION#conflict#1',
      name: 'Conflict Test',
      version: 1,
      type: 'TEST',
    }
    // First update succeeds
    const res1 = await request(config.apiBaseUrl)
      .put(`/items/${payload.id}`)
      .send(payload)
    // Second update with same version fails
    const res2 = await request(config.apiBaseUrl)
      .put(`/items/${payload.id}`)
      .send(payload)
    expect(res1.statusCode).toBe(200)
    expect(res2.statusCode).toBe(409) // Conflict
  })
})
独立したバージョンシーケンス
describe('Independent Versioning', () => {
  it('should maintain independent version sequences', async () => {
    const item1 = {
      pk: 'TEST#SEQ1',
      sk: 'item#1',
      id: 'TEST#SEQ1#item#1',
      name: 'Sequence 1',
      version: 0,
      type: 'TEST',
    }
    const item2 = {
      pk: 'TEST#SEQ2',
      sk: 'item#1',
      id: 'TEST#SEQ2#item#1',
      name: 'Sequence 2',
      version: 0,
      type: 'TEST',
    }
    // Both items start at version 1
    const res1 = await request(config.apiBaseUrl)
      .post('/items')
      .send(item1)
    const res2 = await request(config.apiBaseUrl)
      .post('/items')
      .send(item2)
    expect(res1.body.version).toBe(1)
    expect(res2.body.version).toBe(1)
    // Update first item
    const updateRes = await request(config.apiBaseUrl)
      .put(`/items/${item1.id}`)
      .send({ ...item1, version: 1 })
    expect(updateRes.body.version).toBe(2)
    // Second item still at version 1
    const getRes = await request(config.apiBaseUrl)
      .get(`/items/${item2.id}`)
    expect(getRes.body.version).toBe(1)
  })
})
ベストプラクティス
- 更新操作には常にバージョン番号を含める
 - アプリケーションでバージョン競合エラーを適切に処理する
 - 競合を処理するための適切なリトライ戦略を使用する
 - リトライには指数バックオフを実装することを検討する
 - APIドキュメントにバージョン管理を記載する