API通信を伴うThe Elm Architecture を流れをサクっと体験する

最近 Elm を始めた者です。
Elmでなにかを作る際にAPI連携をする際の基礎的な実装を知ろうと思い、まずはスモールステップの為に「OKと返すAPIを叩いて、画面上にOKと表示する。」という最小単位のアプリケーションを作ってみたのでその過程をメモしておこうと思います。


Elmの[チュートリアル](https://guide.elm-lang.jp/)を触って次にAPIを使ってなにか作ろうとしている人の手助けになれば幸いです。

また初心者なので、間違った解釈をしている可能性があるので、詳しい方はご指摘をお願いします。 :bow: 

## The Elm Architecture　とは
普通のJavascriptの場合は、実装する際にReactやAngular、Vueなど言語実装とは別にフレームワークがあり、開発者がそれを自由に選ぶ事ができるのに対して、Elmという言語そのものがフレームワークを持っている。Elmを動かす時は、このフローを理解する事から始まる。

この制約があるおかげで、
- データのフローが一方向になる。
- 副作用が分離される。
- 一度覚えればElmで書く際に、新たにフレームワークを覚える必要がない

などの利点がある。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/202479/c39c8992-fdb8-9167-766a-77befca0487c.png)
出典: https://ralfw.de/2016/08/elm-architecture-flow/

この記事では上記の図をもとに実装の流れを書いていこうと思う。

## main
図の "elm" の部分。ここを見れば、The Elm Architecture のメインは 

- init
- update
- subscriptions
- view

だと言う事がわかるだろう。

``` elm
main =
    Browser.element
        { init = init
        , update = update
        , subscriptions = subscriptions
        , view = view
        }
```
上記４つに加えて、状態を表す「model」、命令を表す「Cmd」「Msg」などが登場する。

## model
modelは画面が持つ状態を表すオブジェクトである。
Modelという[タイプエイリアス](https://guide.elm-lang.jp/types/type_aliases.html)をつけて取り扱う。
今回は、APIを叩いて受け取った値だけをモデルとして持てばいいので、以下のようなシンプルな構成になる。

``` elm
type alias Model =
    { message : String }
```

## Msg
Msgは図の「Command」の部分で用いられる。init,update,subscriptions,view間でのやり取りのメッセージを意味する。
Msgは[カスタムタイプ](https://guide.elm-lang.jp/types/custom_types.html)で定義する。
今回は、APIを叩いた結果を持つ HealthCheckという型を定義する。
APIの結果値は、成功もしくは失敗する可能性もある為、 [Result型](https://package.elm-lang.org/packages/elm/core/latest/Result) で定義する。

``` elm
type Msg
    = HealthCheck (Result Http.Error String)
```

## init
initの図を見ると、() を受け取り、 modelと command を返す関数なので、
型は init: () -> (Model, Cmd Msg)と表現される。

画面を開いた際に、APIを叩いて欲しい為、getHealthCheckというAPIを叩く関数をCmdに入れる。
modelには初期値として空のmessageを入れる。

``` elm
init : () -> ( Model, Cmd Msg )
init _ =
    ( { message = "" }, getHealthCheck )
```

## getHealthCheck
APIを叩く実装は以下のように定義する。

``` elm
getHealthCheck : Cmd Msg
getHealthCheck =
    Http.send HealthCheck (Http.get getUrl decoder)


getUrl : String
getUrl =
    Url.crossOrigin "http://localhost:8080" [ "health" ] []


decoder : Decode.Decoder String
decoder =
    Decode.field "message" Decode.string
```

## view
Viewは、Modelを受け取り、Html Msgを画面上(Elm)へ返す関数。
ここでは受け取ったModelのmessageを表示するようにする。

もし、initに Cmdを何も指定していない(Cmd.none)場合は、空文字が表示されるが、今回は、CmdにgetHealthCheckを設定しているのでそのままフローはupdateに移動する。

``` elm 
view : Model -> Html Msg
view model =
    div []
        [ h1 [] [ text model.message ]
        ]
```

## update
Updateは Msg と Model を受け取り、 Model,Cmd Msg を返す関数である。
受け取った Msgをもとに Modelの状態を更新する役割を持つ。

Msgの内容によって処理が分岐するので、[パターンマッチ](https://guide.elm-lang.jp/types/pattern_matching.html)を用いて分岐をさせる。状態に副作用を与えない為に、[レコード](https://guide.elm-lang.jp/core_language.html)を用いて modelから messageを変更した新しいmodelを生成して返す。

この時、modelは図のように view に渡されて画面が更新される。


``` elm
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        HealthCheck (Ok res) ->
            ( { model | message = res }, Cmd.none )

        HealthCheck (Err _) ->
            ( { model | message = "error" }, Cmd.none )
```

## subscriptions
今回は使用しないので、 Sub.noneを返すように設定する。

``` elm
subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.none
```

## API
API側は golangでサクッと用意する。
来たリクエストが localhost:8000 からのものであれば、 { message: "ok" } と返すだけのサーバーである。
(ここは気分で実装したので無視してください。笑)

``` go
package main

import (
	"github.com/iris-contrib/middleware/cors"
	"github.com/kataras/iris"
)

func main() {
	app := iris.Default()

	c := cors.New(cors.Options{
		AllowedOrigins: []string{"http://localhost:8000"},
	})
	app.Use(c)

	app.Get("/health", func(ctx iris.Context) {
		ctx.JSON(iris.Map{
			"message": "ok",
		})
	})

	app.Run(iris.Addr(":8080"))
}
```
## 全体像
いままでの実装を一つにすると以下のようになる。

実際に動くものは以下のレポジトリにあるのでよろしければ参考にしてください。
https://github.com/ap8322/elm-architecture-training

``` elm
module Main exposing (Model, Msg(..), init, main, update, view)

import Browser
import Html exposing (Html, div, h1, img, text)
import Html.Attributes exposing (src)
import Http
import Json.Decode as Decode
import Url.Builder as Url





type alias Model =
 { message : String }


init : () -> ( Model, Cmd Msg )
init _ =
 ( { message = "" }, getHealthCheck )





type Msg
 = HealthCheck (Result Http.Error String)


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
 case msg of
 HealthCheck (Ok res) ->
 ( { model | message = res }, Cmd.none )

 HealthCheck (Err _) ->
 ( { model | message = "error" }, Cmd.none )





view : Model -> Html Msg
view model =
 div []
 [ h1 [] [ text model.message ]
 ]


subscriptions : Model -> Sub Msg
subscriptions model =
 Sub.none


main =
 Browser.element
 { init = init
 , update = update
 , subscriptions = subscriptions
 , view = view
 }


getHealthCheck : Cmd Msg
getHealthCheck =
 Http.send HealthCheck (Http.get getUrl decoder)


getUrl : String
getUrl =
 Url.crossOrigin "http://localhost:8080" [ "health" ] []


decoder : Decode.Decoder String
decoder =
 Decode.field "message" Decode.string
```

## 動かしてみる
実際に動かしてみると「OK」という文字が画面上に表示される。失敗した場合の動作確認をしたい場合は、　

``` go
AllowedOrigins: []string{"http://localhost:8000"},
```
のホスト部分を適当な値にすれば "error" という文字列が表示されるようになるはずです。