-
ポイントが付与されるイベント毎に期限がある
-
イメージするレコードは以下のようなもの
| id | userId | point | expirationDate | createdAt | | 1 | u1 | 100 | 2020-07-01 | 2020-04-01T00:00:00.000Z | | 2 | u1 | 500 | 2020-08-01 | 2020-05-01T00:00:00.000Z | | 3 | u2 | 1000 | 2020-09-01 | 2020-06-01T00:00:00.000Z | | 4 | u1 | -50 | null | 2020-06-15T00:00:00.000Z | | 5 | u1 | -100 | null | 2020-06-30T00:00:00.000Z | | 6 | u1 | 300 | 2020-12-01 | 2020-09-01T00:00:00.000Z |
-
expirationDate
はUTCで0時を表すとする
一見すると合算すれば各ユーザーの現在のポイントをちゃんと算出できそうである。 しかし「期限」という状態を持ったレコードであるため、実はこれはうまくいかない。
u1
のポイントを2020-06-30
時点で計算すると100 + 500 - 50 - 100 = 450
となるので問題ない。
期限が切れのポイントがあると問題が出てくる。
2020-07-30
時点で計算すると500 - 50 - 100 = 350
となり、実際は期限が切れる前に使った100
が丸々なかったことになってしまう。
利用したポイント(マイナスのポイント)にもexpirationDate
を持たせる?
これは複数のポイントを跨いで利用しているポイントの部分でうまく対応できない。 なのでさらにポイント利用時のレコード挿入方法に工夫がいる。
| id | userId | point | expirationDate | createdAt |
| 1 | u1 | 100 | 2020-07-01 | 2020-04-01T00:00:00.000Z |
| 2 | u1 | 500 | 2020-08-01 | 2020-05-01T00:00:00.000Z |
| 3 | u2 | 1000 | 2020-09-01 | 2020-06-01T00:00:00.000Z |
| 4 | u1 | -50 | 2020-07-01 | 2020-06-15T00:00:00.000Z |
| 5 | u1 | -50 | 2020-07-01 | 2020-06-30T00:00:00.000Z | <= もともとは-100だった
| 6 | u1 | -50 | 2020-08-01 | 2020-06-30T00:00:00.000Z | <= 上に同じ
| 7 | u1 | 300 | 2020-12-01 | 2020-09-01T00:00:00.000Z |
以前はid=5
で一気に-100
していたが、id=5
とid=6
でレコードを分けた。
こうするとそれぞれのマイナスのポイントで適切な有効期限を持つことができて、イベントソーシングの計算もうまくいく。
さらにどの決済で利用したかがわかるように決済のIDなども保存しておくのが良さそうである。 ただしなるべくイベントレコードは状態を持つべきではないので、逆に決済レコードにポイントのIDを覚えさせるようなやり方の方が良い。 でもRDBだとそれはそれで少し面倒くさいことになりそうである。
適切なタイミングで期限が切れたというイベントのレコードを挿入する。
ポイントを計算するときにはexpirationDate
を考慮せずに全部合算する。
| id | userId | point | expirationDate | createdAt |
| 1 | u1 | 100 | 2020-07-01 | 2020-04-01T00:00:00.000Z |
| 2 | u1 | 500 | 2020-08-01 | 2020-05-01T00:00:00.000Z |
| 3 | u2 | 1000 | 2020-09-01 | 2020-06-01T00:00:00.000Z |
| 4 | u1 | -50 | null | 2020-06-15T00:00:00.000Z |
| 5 | u1 | -50 | null | 2020-07-01T00:00:00.000Z | <= 期限が切れたタイミングで挿入
| 6 | u1 | 300 | 2020-12-01 | 2020-09-01T00:00:00.000Z |
この方法の欠点としてはリアルタイムの処理が難しいことにある。 基本的には期限切れのレコードを挿入するのはバッチ処理になる。 ビジネス要件次第だが決まった時間(例えば期限切れする日の0時)にポイントが切れるなどの仕様であれば多少はマシだが、 結局データ量が多くなるとそのようなバッチ処理もどんどん苦しくなると予想される。
remainingPoint
やusedPoint
みたいなカラムを持たせて、ポイントが使われたらそれらのカラムの値を変更する。
これは明らかに状態を持つことになるので、イベントソーシングとは呼べなくなる方法かと思われる。