Skip to content

Instantly share code, notes, and snippets.

@ttokutake
Last active August 24, 2020 06:41
Show Gist options
  • Save ttokutake/cfcd8c22f3680e5c4fceed4645bde4f3 to your computer and use it in GitHub Desktop.
Save ttokutake/cfcd8c22f3680e5c4fceed4645bde4f3 to your computer and use it in GitHub Desktop.
#アーキテクチャー #DB

イベントソーシングでの表現が難しいもの

期限付きポイントシステム

  • ポイントが付与されるイベント毎に期限がある

  • イメージするレコードは以下のようなもの

    | 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が丸々なかったことになってしまう。

対応策1

利用したポイント(マイナスのポイント)にも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=5id=6でレコードを分けた。 こうするとそれぞれのマイナスのポイントで適切な有効期限を持つことができて、イベントソーシングの計算もうまくいく。

さらにどの決済で利用したかがわかるように決済のIDなども保存しておくのが良さそうである。 ただしなるべくイベントレコードは状態を持つべきではないので、逆に決済レコードにポイントのIDを覚えさせるようなやり方の方が良い。 でもRDBだとそれはそれで少し面倒くさいことになりそうである。

対応策2

適切なタイミングで期限が切れたというイベントのレコードを挿入する。 ポイントを計算するときには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時)にポイントが切れるなどの仕様であれば多少はマシだが、 結局データ量が多くなるとそのようなバッチ処理もどんどん苦しくなると予想される。

対応策3

remainingPointusedPointみたいなカラムを持たせて、ポイントが使われたらそれらのカラムの値を変更する。 これは明らかに状態を持つことになるので、イベントソーシングとは呼べなくなる方法かと思われる。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment