doilux’s tech blog

ITに関する備忘録。 DDP : http://doiluxng.hatenablog.com/entry/2018/01/01/195409

StateにないときはEventを参照するパターン

いわゆるイベントドリブンな設計が好きだ。

CREATE TABLE ord_event(
    id NUMBER(10) PRIMARY KEY,
    payment_type VARCHAR(255) CHECK payment_type IN('full', 'installments'),
    ord_at DATE NOT NULL
);

CREATE TABLE complete_event(
    id NUMBER(10) PRIMARY KEY REFERENCE ord_event(id),
    com_at DATE NOT NULL
);

エンティティを取り出すときは以下のようなクエリを使う。

SELECT
    o.id
    , o.payment_type
    , o.ord_at
    , c.comp_at
FROM
    ord_event o LEFT JOIN complete_event c
    ON o.id = c.id
WHERE
    o.id = #{id}
;

ここでこんな要件が出てきたとします。

「お客様は契約開始までの間は支払い種別を変更できるようにしてー」

となるとイベントドリブンな設計に基づくとこんなテーブルができる。

CREATE TABLE pt_change_event(
    id NUMBER(10) PRIMARY KEY,
    ord_id NUMBER(10) NOT NULL REFERENCES ord_event(id),
    payment_type VARCHAR(255) CHECK payment_type IN('full', 'installments'),
    changed_at DATE
);

参照する時のクエリはこうなるはず。

SELECT
    o.id
    , COALSCE(t.payment_type, o.payment_type)
    , o.ord_at
    , c.comp_at
FROM
    ord_event o LEFT JOIN complete_event c
    ON o.id = c.id
    LEFT JOIN (
        SELECT
            id
            , ord_id
            , payment_type
            , max(id) partition by(ord_id order by changed_at) as max_id
        FROM
            pt_change_event
    ) t
    ON 
        t.id = t.max_id
        AND o.id = t.ord_id
WHERE
    o.id = #{id}
;

実に複雑。なんで、ステートテーブルを作った方がいいと思う。 参照する時のクエリはこうなるはず。

SELECT
    COALSCE(s.id, o.id)
    , COALSCE(s.payment_type, o.payment_type)
    , COALSCE(s.ord_at, o.ord_at)
    , COALSCE(s.comp_at, c.comp_at)
FROM
    ord_state s RIGHT JOIN ord_event o 
    ON s.id = o.id
    LEFT JOIN complete_event c
    ON o.id = c.id
WHERE
    o.id = #{id}
;

COALSCEを使えば「ステートテーブルになかったときはイベントを見に行く」という実装ができるので、システムを稼働してからステートテーブルを作った場合でもデータ移行なしでステートに移行できる。

まあ、もちろん、更新が走ったときはステートを更新しなきゃなので、upsert(あればupdateなければinsert)みたいなことはしなきゃだけど