読者です 読者をやめる 読者になる 読者になる

CQRSのコマンドで戻り値を取りたい場合はどうするのか(cqrs command return value)

DDD CQRS

----- 2016/10/23 追記 ---------
コマンド操作が実行されると、
controller で アプリケーションサービスを呼び出す実装ではなく、
controller でコマンドを発行し、
bus で受信し、
saga で処理していくような実装を想定しています。

詳しくは .NET本 を見るといいかと・・・。
----------------------------------


----- 2016/10/23 追記 2 ---------
DDDの.NET本の実装だとC#の async/await でイベント送信を非同期にしながらも、
同期的に戻り値返してたから、
これが正解なのかね・・・って感じです。

                                                                  • -


コマンドは非同期で実行されるので、
「ログイン」や「データを登録した後にそのデータの詳細画面にリダイレクトする」のような実装ができない。

では、どーやって実装するのか?


まずログインについてはコマンドではなくクエリで実装する。

ログインはPOSTで処理するのでコマンドのイメージがあるかもしれないが、
やってることは「IDとパスワードに合致するアカウントが存在するか」+「sessionにログイン情報をセット」の2つになると思う。
認証にアクセストークンを利用する場合は「sessionにログイン情報をセット」は不要になって、
単にアクセストークンを返すだけになるかな。

このアカウント存在チェックはクエリで、
sessionのセットはコマンドになるが、
これはCQRSの「コマンドかクエリかよく分からん処理」に該当する気がする。
他にはスタック操作とかが代表的。

なので、HTTP は POST にして、クエリで処理してしまえばいーかと。



次に「データを登録した後にそのデータの詳細画面にリダイレクトする」についてだが、
これの何が問題かというと、
詳細画面にリダイレクトする際に、
登録したデータをユニークに判別するIDが必要になる。
このIDが入力時に決まっていれば問題ない。
以下のようにリダイレクトすればいい。

func CreateItem(id, name string) {
    //非同期でコマンド発行
    bus.SendDommand(NewCreateItem(id, name))

    //詳細画面にリダイレクト
    http.Redirect("/items/" + id)
}

ここで1つのポイントとなるのが、事前にIDを決めれるかどうか。
例えば、上記の例で言うと、name 自体がユニークであれば、問題なくリダイレクトできる。
(さらに id も不要になる)

func CreateItem(name string) {
    //非同期でコマンド発行
    bus.SendDommand(NewCreateItem(name))

    //詳細画面にリダイレクト
    http.Redirect("/items/" + name)
}

ちなみに、nameのバリデーションに引っかかったり、
リダイレクトした段階でデータがなくて 404 になるってのは
今回とは別の問題ではあるが、
通知方法を考える必要がある。

また、IDを自前で生成する仕組み(UUID)にすれば、
コマンド発行前にIDを指定できるので、
このパターンを採用すると解決できる。

func CreateItem(name string) {
    id := uuid.GenerateStr()

    //非同期でコマンド発行
    bus.SendDommand(NewCreateItem(id, name))

    //詳細画面にリダイレクト
    http.Redirect("/items/" + id)
}

ただ、DBで自動採番されたIDを利用する場合は、この方法が利用できない。
(特に既存システムとか)
じゃあ、どうするのかというと、諦めて同期処理にするしかないと思う。

非同期を諦めて同期処理にすると以下になる。
別に書く必要もないと思うけど。

func CreateItem(name string) {
    //同期でコマンド発行
    id := bus.SendDommand(NewCreateItem(name))

    //詳細画面にリダイレクト
    http.Redirect("/items/" + id)
}

しかし、コマンドを同期処理にしてしまうと、
CQESの特性を失う(というか、CQRSじゃないという考えもある)ので可能な限り避けたい。

なので、「データを登録した後にそのデータの詳細画面にリダイレクトする」という仕様を
「データを登録した後に一覧画面にリダイレクトする」とかに変更するという
全然技術関係ないじゃんっていうのが解決策なのかなと思っている。

リダイレクトしたときに、
一覧画面を登録された順位ソートしておいて確認しやすくするとか、
一覧画面の目立つところに「最近登録されたデータ」を数件表示する場所を作るとか、
こういった方法で避けるしかないのかなと・・・。。

そもそもCQRSを採用した時点である程度複雑な仕様だったり、
パフォーマンスがシビアだったりするので、
この程度の仕様変更は問題ないのかなと思ったり・・・

この程度の仕様変更よりも結果整合性の方がよっぽど面倒だと思うので、
一覧画面のUIが・・・とか、リダイレクト先が・・・とか小さな問題なのではと思ったり・・・

しなくもない。

他に良い解決方法あったら教えてほしいです。