UIから考えるモデリング in Elm
Modelは大切
皆さんは何を考えながらモデリングしてますか?まずはElm Architectureを見てみましょう。
ViewはModelに依存しUpdateもModelに依存しています。したがってModelの影響の大きさは言わずもがなです。
ちょっとしたModelの修正も影響範囲が多くて溜息をつくことは少なくありません。影響は少ないほうがいいですよね。
今回はモデリングについて考えてみましょう。
UIから考える
今回はこの動画にある方法を使ってモデリングをやってみようと思います。
リチャードさんはまずはUIから考えようと言っています。インライン編集可能なブログ記事のページを考えてみましょう。
TitleとStoryという本文で構成されたブログ記事のページです。記事の作成者はインライン編集ができます。
ペンのアイコンをクリックすると右のページようなインライン編集状態になります。Saveボタンをクリックすると左のページのTitleが編集したものに置換されます。
みなさんもこのようなページをモデリングしてみてください。それでは次に私がモデリングしてみます。
type Article = Article { title : Title, story : Story } Property type Property = ReadOnly | WriteTitle Title type alias Title = String type alias Story = String -- MODEL type alias Model = { article : Article }
Titleのインライン編集に着目してモデリングしてみました。皆さんのモデリングと見比べてどうでしたか?
それでは次のモデリングと見比べてみましょう。
type Property = ReadOnly | WriteTitle Title type alias Title = String type alias Story = String -- MODEL type alias Model = { title : Title -- READ/WRITE , story : Story -- READ/WRITE , property : Property -- READ/WRITE }
前者と後者は何が違うのでしょうか?後者はレコードの要素をいつでも操作することができます。
そのためPropertyがReadOnlyであってもtitleとstoryは変更できます。もちろんそのようにしないロジックを構築すれば防ぐことができます。
update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of ToggleEditTitle -> ( { model | property = WriteTitle model.title }, Cmd.none ) SetTitle title -> case model.property of WriteTitle _ -> ( { model | property = WriteTitle title }, Cmd.none ) ReadOnly -> ( model, Cmd.none ) SaveTitle -> case model.property of WriteTitle title -> ( { model | title = title, property = ReadOnly }, Cmd.none ) ReadOnly -> ( model, Cmd.none )
それでは前者の場合はどうでしょう。
module Article exposing (Article, empty, mapTitle, toggleTitle) type Article = Article { title : Title, story : Story } Property type Property = ReadOnly | WriteTitle Title type alias Title = String type alias Story = String empty : Article empty = Article { title = "", story = "" } ReadOnly toggleTitle : Article -> Article toggleTitle (Article edits property) = case property of ReadOnly -> WriteTitle edits.title |> Article edits WriteTitle title -> ReadOnly |> Article { edits | title = title } mapTitle : (Title -> Title) -> Article -> Article mapTitle modifier ((Article edits property) as article) = case property of ReadOnly -> article WriteTitle title -> WriteTitle (modifier title) |> Article edits
module Page.Article exposing (..) import Article exposing (Article) -- MODEL type alias Model = { article : Article } -- UPDATE update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of ToggleEditTitle -> ( { model | article = Article.toggleTitle model.article }, Cmd.none ) SetTitle title -> ( { model | article = Article.mapTitle (always title) model.article }, Cmd.none ) SaveTitle -> ( { model | article = Article.toggleTitle model.article }, Cmd.none )
いかがでしょうか。モジュールを2つに分けてみました。
これならArticleはReadOnlyのときにtitleを変更できません。なぜならそのような関数がArticleモジュールから公開されてないからです。
また前述のパターンと異なり、WriteTitleコンストラクタも公開していません。このためArticleモジュール内部にArticleの実態は隠蔽されています。
この方法をOpaque Typeと呼びます。テクニックの一つですが動画では必須とまで言われています。
これで、Articleモジュールを編集してもUpdateやViewに影響が出ないように作ることができました。ネストが浅くなり多少見やすくなってないでしょうか。
また、title編集時の状態を WriteTitle Title
と定義しているのもポイントです。
type alias Model = { title : Title , story : Story , editableTitle : Maybe Title , editableStory : Maybe Story }
少し定義を変えてみました。それではこの状態はとり得るのでしょうか。
type alias Model = { title : Title , story : Story , editableTitle : Just Title , editableStory : Just Story }
インラインで編集可能なのは一箇所だけとします。するとeditableTitleとeditableStoryの両方がこのような状態を取ることはありえません。
このようなアプリケーションにありえない状態を作り出すことが可能なモデリングを避けることでバグの少ないアプリケーションを構築できます。不可能な状態はモデリングで防ぐことができます。
サマリ
- UIから考えることでモデリングができる
- Oppaque Typeで変更に強いモデルを作ることができる
- 不可能な状態 (Impossible state) を排除することができる
所感
またもや動画からモデリングの方法の一部を紹介しました。Elmでアプリケーション開発をしているとモデルを変更する機会は多々訪れます。
そのたびにModel, Update, Viewのすべてを変更するのはとても大変です。今回の方法を用いてなるだけ変更の影響がもれないように気をつけてみてください。
しかし、モデリングのポイントはこれだけではありません。UI StateとDomainをどのようにモデリングするのかという話題もあります。
フロントエンドでDDDをしようものならもっと複雑になることでしょう。一概にこれが正解というわけではないので皆さんの課題に沿った最善の方法を模索していきましょう。
そしてどんどん私に教えてください。 (違