SAMパターンというのを勉強している。
この記事は少しだけはてブでバズったが、実際コレを読んだだけでは例えば実際に実装に落としたときにどういうデータ・フローになるのか、というところまでは若干理解しづらい。この記事の作者のDubrayはsam.js.orgでいろんなサンプルコードを紹介しているので、それらを参照すると若干理解が捗る。
ちなみに今のところ自分の中で整理しきれた雑なSAMのデータ・フローはこれ。
パッと見の雰囲気はFluxアーキテクチャと全く違う、という感じではない。SAMはState-Action-Modelのアクロニムで、ベースになるのもその3つの要素だ。それぞれの要素の名前はこれまでのアーキテクチャを知っている人からすると、比較的馴染みやすいものではあるかと思うが、SAMの場合はそれぞれが担う責務が若干異なる。なので、たとえばStateと聞いたときにそれが何を意味するのかというのは、既存のアーキテクチャの考え方からではなく、SAM独自の考え方から理解するのがよい。
ということで、SAMにおける主要な3つの要素に焦点をあてて、sam.js.orgでの解説を元に、簡単な責務の説明をしてみたいと思う。
Model
SAMにおけるModelとは、Actionから受け取った計算結果を保持し、それをビューに反映させるトリガを提供するだけの存在だ。すなわち、ここにはドメインロジックなどは一切入り込まない。
The model contains all the application state and (logically) exposes a single method: present(data)
上のデータ・フロー図ではModelの中にPresentという関数がいるが、これがModelの唯一持つ関数としての責務だ。Presentが行う大きな責務は、フロントエンド・アプリケーションで言うところのレンダリング処理のトリガだ。現在の状態を元に、ビューに状態を反映させるトリガをキックする。また、そこで使われる「現在の状態」は全てModelが持っている。
The model is also solely responsible for the persistence of the application state.
加えて、この説明が示すように、Modelはデータの保持の責務(Fluxで言うところのストア)も持っている。SAMは単なるアーキテクチャのため「メモリに載せる」「localStorageを使う」などの、どう永続化するかという「方法」の点では一切の言及がない。この責務をどう実装するかという点は自由だ。
Action
SAMにおけるアクションは、データを受け取り、そのデータをもとに実行したビジネスロジックの結果をModelへPresentを介して送りこむ関数だ。Actionのトリガはビューと、後述するNAPによって行われる。
Actions are responsible for implementing context specific logic.
SAMパターンにおいて、Actionとはドメインモデルに対する操作を行う場所だが、実際にここをどう実装するかに対する方針はSAMでは言及されていない。再利用可能なドメインモデルに具体的な操作は委譲し、ここではそれらのビジネスルールを用いて得られる結果のみをモデルに対して送るというのがおそらく良いかと思われる。
In the context of SAM, actions play another important role with respect to invoking 3rd party APIs.
加えて、ActionはAPIコールなどを行う場所でもある。
State
the State does not hold any “state”, it is a pure function which computes the view and the next-action predicate, both from the model property values.
SAMにおけるStateはFluxアーキテクチャのそれとは異なる。Reduxを使っている人なら、Stateと聞くとストアの中にあるデータ構造のことをイメージするかもしれないが、SAMの場合にはStateの具体的な責務は、Modelが保持しているデータ構造と、ビューで必要とされるデータ構造間のインピーダンス・ミスマッチの解消に近い。StateはModelから受け取ったデータを元にビューで必要となるデータに変換を行う関数でしかない。
the State is responsible for invoking the next-action predicate (nap)
NAPはNext-Action-Predicateのアクロニムで、Stateが持つもう一つの責務を指すもの。これは、見方を変えればStateの副作用だ。NAPは現在のModelの状態から、次にトリガされる必要のあるActionを呼び出す。sam.js.orgのStateセクションでは、ロケット発射台のプログラムが例になっている。ロケットのカウントダウン・カウンタを減算していくActionがあり、そのアクションによってカウンタが0になったとき、Modelの状態を見て自動的にロケット発射のActionをトリガする。
NAPの利点は、ビューを単なるStateの写像にするために、特定条件下でトリガされるアクションの条件分岐という責務をビューから取り払うことができるという点だ。Fluxアーキテクチャにおいて、Actionの発火元は常にビューだったため、例えばswitch文で記述される状態に関連したアクションの発火(たとえば状態を見てナビゲーターによって遷移を起こさせるなどのロジック)をビューに記述していく必要があった。これは場合によってはビューの肥大化につながることもあるが、SAMはNAPによってビューからそのような責務を分離している。
まとめ
SAMパターンが最も意図しているところは簡単で、ビューがアプリケーション全体に影響を及ぼすことがないようにしよう! というだけのことだ。そのために様々なMVCやらMVVMやらが考案されてきたが、SAMはより関数型のアプローチをしようとしているアーキテクチャだということが分かる。ほとんど日本語の資料がないので、少しづつSAMの公式ページを読んでいくしか今のところ学ぶ手段はないが、それでも充分読んで見る価値がある。
また、大事なことはSAMというフレームワークやライブラリがある、ということではないということだ。つまり、SAMというアーキテクチャに関して、どう実装するかは十人十色だ。実装にReactを使うのか、それともChooかVanillaJSか、という点はSAMにおいてはまったく重要ではない。大事なのはアーキテクチャとしての考え方だ。
とはいえ、InfoQで掲載された記事についているぶコメは、記事を読んでそうに見えて、いくつかはあんまり関係のないものがあった。「サンプルコードでXSSできるから微妙なのでRedux使っとこう」「ナマのJSで良いという主張だということが分かった」「htmlを文字列連結で書くなんて」というあたりのコメントだが、これはおそらくアーキテクチャ自体ではなく、それを実現するために使う言語やツール、すなわち「手段」に関して興味が寄りすぎている。
手段それ自体への興味はとても良いが、あまりにそこへ指向の比重が寄りすぎてしまうと、どうしても手段が設計に影響を及ぼしかねない。アプリケーションを作るにあたって、手段がなにかを規定してしまうのは大抵の場合よくない。技術をやっていると、どうしてもそこが面白くなってしまうことが多いが、一歩身を引いて設計という観点からものを見直すというのは、いつでも重要なのではないかと思う。