LoginSignup
9

More than 5 years have passed since last update.

posted at

updated at

Serverless Framework × AWS Lambda で始めるサーバサイド Elm

はじめに

この記事は、クライアントサイドの実装に特化した言語仕様、アーキテクチャを持っているElmあえてサーバサイド実装をしてみたまとめの記事になります。インフラのことは考えずサーバレスで作っていきたいのでAWS LambdaServerless Frameworkを使ってみました。かなり荒削りなので質問があればコメント欄、もしくはTwitterで気軽にお声がけいただけるとうれしいです。

なぜElmでサーバサイド?

モチベーションは非常にシンプルです。フロントエンドとバックエンドの両方でElmが使える、それに尽きます。メリットとしては以下が考えられると思います。

  • モデルの構造(データ型)に差異が出ない
  • APIのインターフェースの定義をフロントエンド側からも参照できる
  • 全てがElmである(?)

登場人物

Elm

言わずと知れたHaskellライクな文法を持った言語、アーキテクチャです。

AWS Lambda

AWSが提供する、書いたコードをzipにかためてアップロードするだけでそのコードをクラウド上で実行することが可能になるサービスです。今回はElmが吐き出すJavaScriptのコードを実行するためのインフラとして利用します。サーバレスやAWS Lambdaについて気になる方は以下の記事が分かりやすいので見てみると良いかもしれません。

Serverless Framework

例えばAWS Lambdaである程度の規模のアプリケーションを開発する場合、デプロイやアプリケーションの設定などが複雑になってきます。それを用意にしてくれるアプリケーションフレームワークがServerless Frameworkです。

elm-serverless

ElmでAWS Lambdaを動かすためのライブラリです。以下を内包しています。

  • AWS LambdaのNode.jsのコードで受け取ったHTTPリクエストをElmに流し込み、レスポンスをElmから受け取るためのNode.jsのパッケージ
  • Node.js側から受け取ったリクエストをElm側で処理し、レスポンスを構築するためのElmパッケージ

elm-serverless の仕組み

今回は非常にシンプルな、Todoの閲覧しかできないアプリ(登録/更新はできません...)の一部を雑に実装してみたので、それを作った流れを追いながらelm-serverlessの仕組みをご紹介します。扱うTodoの型はこんな感じ。今回はDBとの連携が試せてないのでアプリに静的なデータを埋め込んでそれを扱います:bow:

dummyTodos : List Todo
dummyTodos =
    [ { id = "1", title = "This is todo1", finished = False }
    , { id = "2", title = "This is todo2", finished = True }
    , { id = "3", title = "This is todo3", finished = False }
    ]


type alias Todo =
    { id : String
    , title : String
    , finished : Bool
    }

以下がGitHubのリポジトリになります。
https://github.com/otofu-square/serverless-elm-todo-api

環境構築

elm-serverless を始めるには以下のものが必要です。

  • Node.js
  • yarn(or npm)
  • AWSアカウント

それではまずServerless Frameworkをインストールして、プロジェクトを作りましょう。Serverless Frameworkでプロジェクトのボイラープレートを作成する際はcreateコマンドを使います。

$ yarn global add serverless
$ serverless create --template aws-nodejs --path serverless-elm-todo-api
$ cd serverless-elm-todo-api

プロジェクトのディレクトリに移動して、必要なライブラリ群をインストールします。ElmのコンパイルやライブラリのバンドリングはWebpack先生頼りなので、必要なライブラリ

$ yarn add --dev \
  elm elm-serverless \ # Elm
  serverless-webpack webpack uglifyjs-webpack-plugin elm-webpack-loader # Webpack関連 

Elmで使うためのライブラリもインストールしておきます。HTTPリクエストやレスポンスではJSONを簡単に扱いたいのでelm-decode-pipeline、またURLをパースして取り扱いたいのでurl-parserを導入しています。
elm-packageは一度に複数インストールできないのがちょっと面倒ですよね...)

$ yarn elm package install elm-lang/core && \
  yarn elm package install elm-lang/http && \
  yarn elm package install ktonon/elm-serverless && \
  yarn elm package install ktonon/url-parser && \
  yarn elm package install NoRedInk/elm-decode-pipeline

だいたい必要なものは以上です。それでは実際にアプリを書いていきましょう。

serverless.yml, webpack.config.jsを書く

今回は横道にそれそうなので割愛します:bow:
気になる方は自分のリポジトリにあるserverless.yml, webpack.config.jsを見ていただけるともしかしたら参考になるかもしれません。

エントリーポイントとなるjsファイルを書く

まずはじめに、AWS Lambdaがリクエストを受け付けるエントリーポイントとなるNode.jsのコードを実装します。
https://github.com/otofu-square/serverless-elm-todo-api/blob/master/src/api.js

const elmServerless = require('elm-serverless');
const elm = require('./API.elm');

module.exports.handler = elmServerless.httpApi({
  handler: elm.Hello.API,
  requestPort: 'requestPort',
  responsePort: 'responsePort',
});

このコードでは、どのElmファイルをハンドラとして使用するかを設定し、必要があればElm側にJavaScriptの関数を渡すことができます。渡した関数はElm内で実行することが可能です。今回は特に使用しません。

Elmのハンドラを書く

Elmのハンドラを書いていきます。ここが先程のエントリーポイントのjsとElmを繋ぎこむコードとなるため、jsから受け取ったHTTPリクエストを処理してレスポンスを構築する部分を実装していくことになります。

port module Hello.API exposing (main)

import Serverless
import Conn exposing (..)
import Router exposing (router, urlParser)


main : Serverless.Program () () Route () ()
main =
    Serverless.httpApi
        { configDecoder = Serverless.noConfig
        , initialModel = ()
        , parseRoute = urlParser
        , update = Serverless.noSideEffects
        , interop = Serverless.noInterop
        , requestPort = requestPort
        , responsePort = responsePort
        , endpoint = router
        }


port requestPort : Serverless.RequestPort msg


port responsePort : Serverless.ResponsePort msg

main関数はServerless.Program型になっており、これはElmのcoreのProgramが元になっています。具体的にはServerless.httpApiに対して8つのメンバを持つレコードを渡してあげることで処理を書くことができます。全ての説明は割愛しますが、今回主に実装したのはAWS Lambdaが受け取ったURLをパースするparseRouteと、レスポンスを構築するendpointの部分になります。他のパラメータについては公式のAPI Docに簡単な説明があります。
http://package.elm-lang.org/packages/ktonon/elm-serverless/latest/Serverless

parseRouteの実装

ルーティングの型と、それに対するパーサを書いてあげることでURLのハンドリングが行えるようになります。今回実装したルーティングはGET /todosGET /todos/:idの2つのエンドポイントです。

type Route
    = TodoIndex -- GET /todos
    | TodoShow String -- GET /todos/:id
...
urlParser : String -> Maybe Route
urlParser =
    UrlParser.parseString <|
        oneOf
            [ map TodoIndex (s "todos") -- GET /todos
            , map TodoShow (s "todos" </> string) -- GET /todos/:id
            ]
...

endpointの実装

endpointではURLをパースした結果をパターンマッチで抽出して、それぞれのエンドポイントに対する処理とレスポンスの組み立てを行います。

router : Conn -> ( Conn, Cmd msg )
router conn =
    case
        ( method conn
        , route conn
        )
    of
        ( GET, TodoIndex ) ->
            respond ( 200, jsonBody <| todosEncoder dummyTodos ) conn

        ( GET, TodoShow id ) ->
            case (getTodoById id dummyTodos) of
                Just todo ->
                    respond ( 200, jsonBody <| todoEncoder <| todo ) conn

                Nothing ->
                    respond ( 404, jsonBody <| errorEncoder <| "Not Found id: " ++ id ) conn

        _ ->
            respond ( 405, jsonBody <| errorEncoder "Method not allowed" ) conn

JSONの組み立てにはエンコーダを用意してそちらを使っています。

デプロイ

ここまでで大体実装が終わり、デプロイ出来るようになります。AWSアカウントのクレデンシャルを環境変数にセットしてあげた状態で

$ NODE_ENV=production serverless deploy

を実行してあげるとAWS Lambdaに実装したアプリがデプロイされます。お手軽。
デプロイが完了するとエンドポイントのURLが表示されていると思うので、そちらにリクエストすると結果が返ってくるはずです。

さいごに

Elmでサーバサイドがまさか書けるとは思っていませんでしたが、小さなアプリケーションであればElmでフロントエンドもバックエンドも両方書くのはそこまで非現実的ではないなと少し希望を持てました。しかしながら実際にアプリ内からDBを扱ったりHTTPリクエストを飛ばしたりといったことはまだ試せていないため、そこの部分がうまく解決できるかで命運が分かれそうです(elmのaws-sdkのラッパーもありそうだったが使い勝手が微妙そうだった)。あとこれは自分の熟練度の問題ですがファイル分割が結構難しいですね...気を抜くと循環参照になっちゃったりしてました。
時間不足感が溢れ中途半端なところで記事にしてしまったのが少し悔しいので、今後も継続してelm-serverlessは追っていきたいと思います:muscle:

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
9