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

CQRSのコマンドでユーザーの入力エラーを返したい

CQRS

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


以下は色々調べた結果です。


過去にも似たような記事を書いたけど・・・・
http://d.hatena.ne.jp/pospome/20161016/1476609958


ユーザーが何かを登録する画面で、
入力内容が間違ってた時にエラーを表示させたい。
これを非同期実行のコマンドで、どうやって実装するのかなってのを考えてみた。


メールのフォーマットチェックとか、
名前の最大文字長チェックとかの
入力値単体のエラーであれば、
コマンドを発行する前にバリデーションをかけてしまえばいいと思う。

バリデーションルール自体はドメインモデルに持たせて、
コマンドでそれらに対してバリデーションをかければいい。

以下は Name という VO にバリデーションの実装を持たせているが、
チェック自体は Command 経由で実行するようにしている。

type Name string

func (n Name) valid() bool {
    //ここに名前のバリデーションを実装
    return true
}


type Command struct {
    Name Name
}

func NewCommand(name string) (*Command, error){
   if !Name(name).valid() {
        return nil, erros.New("invalid name")
   }

   return &Command{
        Name:Name,
   }
}


こういったそれ単体でのバリデーションは問題なくユーザーに通知できる。

//これが controller だとする
func AddUser(name string) {
    c, err := NewCommand(name)
    if err != nil {
        render.Json(err.Error) //ここで同期的に入力エラーを通知できる
        return
    }

    //ここは非同期でコマンド発行
    Bus.Send(c)
}


ただ、ドメインロジック上のエラーは同期的に通知できない。
例えば以下のようなケース。

1.ユーザーを登録する際に、すでに登録済みの名前である場合はエラー
2.アイテムを登録する際に、アイテムが登録できる状態になってなければエラー

1.ユーザーを登録する際に、すでに登録済みの名前である場合はエラー

これの場合はユーザーが名前を入力している際に
アプリやWebFE側などのクライアント側でリアルタイムで登録可能かをチェックすることで、
登録ボタンを押した後のサーバ側のチェックでエラーになることは避けれそう。
ただし、サーバ側のチェックでエラーにならないとは限らない。
ユーザー登録している段階だと、メールやマイページでの失敗通知が利用できないので、
その場合の通知は難しい。
ということで、どーやればいーんでしょうね状態。


2.アイテムを登録する際に、アイテムが登録できる状態になってなければエラー

これの場合は登録画面を表示するタイミングで「アイテムが登録できる状態になっているか」をチェックすればいい。
登録できるのであれば入力フォームを出す。
登録できないのであれば、
入力フォームにエラーを表示して入力できなくしたり、
入力フォームへの遷移導線を消したりすればいい。
それでもサーバ側で処理中にエラーになることはあるので、
メールやマイページでの通知は必要になる。


コレ以外で考えたのはエラー捕捉用のエンドポイントを実装すること。
1.入力されたら非同期でイベントを送信する

2.エラー捕捉用のエンドポイント(クエリ用)にリダイレクトして、エラーが発生していないかをチェックする

3.発生していれば、そのまま入力画面にリダイレクトし、エラー + 入力項目を表示する
 発生してなければ一覧画面にもどる。

これの問題は2番のエラーチェック。
非同期で実行されたコマンドがエラー捕捉用のエンドポイントへのリダイレクト前に完了するとは限らないので、
結局エラー捕捉用のエンドポイントで同期する必要がある。
であれば、最初から同期処理でいいのでは?
という我ながらセンスのない実装方法だと思う。

どーしましょうね。