nuits.jp blog

C#, Xamarin, WPFを中心に書いています。Microsoft MVP for Development Technologies。

Blue Monkey Architecture Overviewに対する訂正

先日公開した、つぎの記事について何点か指摘をいただきました。

【Xamarin】Blue Monkeyプロジェクト Architecture Overview コメント入り掲載 - nuits.jp blog

指摘の通り、前述の記事には明らかに誤った記載が何点かありました。読んでいただいた方には大変申し訳ないことをしました。
つぎの点について本稿にまとめます(リンク先の記事も追って修正します)。

  1. 誤りのある部分の訂正*1
  2. 現時点で、正しく理解できていない箇所に対する言及

今回指摘いただいたことで、深く考えずに思い込んでいた点に気がつくことができました。指摘いただいた方、指摘に対する理解を助けていただいた方々には感謝の念がたえません。ありがとうございました。

誤りのあった点について

Modelのメソッドの戻り値がTaskであるという説明

先の記事では次のような記述がありました。

もうひとつは、Modelの呼び出しはメソッド呼び出しになりますが、そのメソッドの戻り値は、型パラメーターを持たないTaskクラスのみであり、メソッドの呼び出し結果は、変更通知という形でVMへ伝えるということです。

ViewModelとModelの相互作用は、つぎのルールに則ることを想定しています(これはそもそもModelがStatefullである前提にありますが、この点については別途後述します)。

  1. ViewModelからModelのメソッド呼び出し、Modelの状態を変化させる
  2. ViewModelはModelから変更通知を受け取り、Modelプロパティの取得する

これは、つぎを想定しているからだと考えています。

  • ModelのStateにたいする観察者が一人とは限らない
  • Modelでは非同期処理が行われる可能性がある

メソッドの戻り値に期待すると他の観察者に変更を通知できませんから、一貫性を保つなら変更通知経由に統一するほうがシンプルでしょう。

また多くのプレゼンテーション プラットフォームは、シングルスレッドモデルを採用しています。そしてMVVMのうちViewとViewModelはプレゼンテーション層に属します。このため、基本的にView・ViewModelはUIスレッド上で処理し、非同期処理はModelにカプセル化すべきなのだと思います。

振り返ってみて、Taskが返されるということは、非同期処理がプレゼンテーション側に漏れ出ているということです。ViewModelで非同期処理は扱いたくありません。そういう観点からするとくvoidのほうが好ましいわけで、「Taskではないとダメだ」という記述は良くありませんでした。*2

私自身、非同期処理はModelでする想定でいましたが、深く考えずasync/awaitを単純に使う想定でいたためこの思い込みを抱えてしまっていました。

この件については、特につぎの「まとめ」が参考になりました。まとめを残してくださった先人に感謝です。

MVVMにおいてVMで非同期は必要か?async void/Taskのどちらが良いか?非同期処理の例外ハンドリングは? - Togetterまとめ

また何度も読み参考にさせていただいている、つぎの記事にも最初からそう書かれていました。正直赤面の限りです。

MVVMのModelにまつわる誤解 - the sea of fertility

Modelがステートフルである前提に立っているが、必ずしもそれが正しいとは限らない

さてそもそも、Modelのメソッドの戻り値がvoidであるという事は、ModelがStatefullである前提に則っています。メソッドの反作用は変更通知と、プロパティの取得で行うという事はModelがState(プロパティ)を持っているという事だからです。

この点について特にAndroidではStateが不安定であり、Transaction Script的なアプローチも考えるべきではないか?という指摘を頂きました。

Android固有の話は良く分かりませんが、デスクトップアプリケーションに比べてスマートデバイスでは、SleepやResumeを繰り返す頻度がたかく、オブジェクトのライフサイクルは安定的ではないのは確かだと思います。こうした中で、オブジェクトとして管理するStateは単一画面(つまりViewModel)に限定し、画面遷移の際に状態を保存し、ModelはStatelessに設計すると全体として堅牢に作りやすいのかも知れません。

特にBlue Monkeyの想定する業務アプリケーションの場合、サーバーサイドに大量のデータがありドメインロジックも一部はサーバーサイドに分散されるでしょう。クライアントに存在する情報は、システム全体の一部分の、しかも特定のタイミングでのスナップショットでしかないわけですから、ModelがStatefullなのは過剰な設計なのかもしれません。

とはいえModelがStatefullの方が作りやすいケースももちろんあるでしょう。*3

ところでModelがStatelessであるとすると、当然プロパティ(State)を持たないわけですから変更通知による反作用の伝達も行いません。つまりModelのメソッドも戻り値が必要となります。Modelの戻り値の選択肢として、Task/void以外にも通常のオブジェクトもあり得るということです。目から鱗が落ちました。

が、ひとつ疑問も生まれました。その点は後述します。

該当アーキテクチャが「普遍的なパターンであるという錯誤」を招く説明だったこと

先に記載したBlue Monkeyのゴールとして、私は次のように書きました。

一定規模のチーム開発に耐えられるアプリケーションアーキテクチャのリファレンス実装を提案することにある

これは完全に私の説明が誤っていましたし、該当のスライドは説明不足すぎました。

Blue Monkeyで想定しているアプリケーションは次のようなものだと私は考えています。

  • 業務アプリケーションで良くある、サーバーサイドに格納された情報へのCRUDを提供するためのスマートクライアント
  • 情報の検索・登録・更新などは基本サーバーのAPIを呼び出して実行する

この例として経費精算アプリを題材としました。

そしてBlue Monkeyの主な目的はつぎの辺りにあると考えています(人によって少し異なるかもしれません)。

  • 前述のようなアプリケーションを構築する上で、参考にできるレベルの品質のアプリケーションサンプルの提示
  • プラットフォームによって異なる実装の組込み例の提示
  • プロダクションコードだけではなく、テストコードの記述例の提示

あまり複雑なアプリケーションや細部まで使い勝手に拘ったアプリは想定していません。目指すところは「業務アプリケーションとして使えるレベル」のサンプルだったはずです。

また前述のようなアプリケーションであっても、その要件によって同じつくりで済まない部分というのは当然出てきます。ですがDIやUnit Testの具体例があれば応用しやすいだろう、という思いもありました。

Blue Monkeyのように作れば、どんなアプリでもうまく作れるよという事を目指していた訳ではないはずですが、今見返すとそうとは取れない資料になっています。「アーキテクチャが固定できないからアーキテクトが必要となる」という意図の指摘をいただきましたがおっしゃる通りです。

これは私の明らかな過失であり、関係者含め申し訳ないことをしました。

まだ良くわかっていない点について

さて、ここまでの過程でまだ自己解決できていない点がいくつかあります。

追記:2017.03.31 次の3.を追記

  1. ModelのメソッドがTaskを返さないとしたら、テストはどう実装するとうまく書けるのか?
  2. ModelがStatelessであった場合、画面をまたがったアプリケーションのStateはどう管理すべきか?
  3. ModelがStatelessで戻り値をとる場合、ViewModel側にTaskがあふれ出てこないのか?

この辺が大きなところです。

2.について。
たとえば、Blue Monkeyでは請求書登録機能は、複数の画面から構成されています。

  1. 請求書の日付・金額などの入力画面
  2. 請求書の画像撮影画面

これらの画面は、登録が完了するまでに行ったり来たりして状態を共有します。現時点ではModelを共有することで解決していますが、ModelをStatelessとした場合どうするのが良いのか?画面遷移のたびに、ストレージに状態を保管するのか?ベストプラクティスが良く分かりません。

この辺りについては今後もゆっくり考えてみたいと思います。

さいごに

本記事ついて、実のところまだ考えが及んでいない点があるのではないかと自信を持てないでいるので、指摘いただいた人に突撃してみようと思います。
またご意見やご指摘あるかたは、ぜひ教えていただけると嬉しいです。

また元の記事の方も、時間の許す限り早急に訂正はしたいと思います。
ひとまずは以上です。

*1:これも私の理解が適切ではない可能性があります。訂正にあたって参考にさせていただいた資料も併せてご覧ください

*2:とはいえTaskを返すが、その扱いを誤らない仕組みを用意するという解決策もありそうです。

*3:MasterDetailのドロワーとコンテンツで状態を共有したい場合などが該当しそうです。