作成: 2017-12-03T00:00:00+09:00

ゲーム販売webアプリケーションSYAKERAKEを支える技術, HaskellとYesodで作られています

Haskell (その3) Advent Calendar 2017 - Qiitaの3日目の記事です.

この前｢Haskellで書かれたwebサービスって何がある?｣と聞かれて, HackageとかStackageのようなHaskellに関連したサービスぐらいしかパッと出せませんでした.

なので, webアプリケーションであるSYAKERAKEがHaskell製であることと, これを構成するライブラリなどを書いていこうと思います. SYAKERAKEがどういうサービスかはサイトを見ていってください. 半分このサービスの宣伝です. お許しください.

この記事を読むことで, 小規模ながらもプロダクションレベルのwebアプリケーションがHaskellで作れるということがわかっていただけると幸いです. 特に実際の周辺環境を書いた記事はあまり無いと思います.

ただ, SYAKERAKEはクローズドソースなので, 現在ソースコードを全公開することは出来ません. 我々してはOSSを推進していて, 実際切り出して公開したOSSは存在するのですが, 雑に作っていった結果公開できない範囲をリポジトリに含んでしまったので全公開は出来ません. 公開していないソースコードを元に技術を語ることをお許しください.

AWSとの連携 というわけでAWS上でSYAKERAKEは動いているので, AWSのサービスと連携しなければいけません. とは言ってもEC2とRDSとS3ぐらいしか使っておらず, S3ぐらいしか特別に対応しないといけないものは無いのですが. 当初はS3のバケットをgoofysでマウントしてファイルシステムとして使っていたのですが, パフォーマンスが低下しているのでは? という疑問からS3はS3として扱うことにしました. 今では私が小手先でプログラミングするよりgoofysに任せておいた方がパフォーマンスが良かったのでは? と疑っています. 工数が圧倒的に足りていないです. それはともかく, AWSのAPIを利用するためにawsパッケージを使っています. amazonkaというパッケージもあるのですが, こちらはamazonka-s3がLensを前提としていたので敬遠しました.

ドキュメント変換にPandocを使用 Haskell利用者以外にも有名なPandocですが, SYAKERAKEでも制限されたMarkdownをHTMLに変換するために利用しています. 他の言語だとFFIとか気にしたりコマンドを動かしたりしないといけないところ, SYAKERAKEはネイティブHaskellなので, ネイティブに内部関数を呼び出すことが可能です. Haskellの利用するかなりの利点だと思います.

Stripeの利用 決済システムにはStripeを利用しています. 自前でクレジットカードデータを保存したりする怖いことはやりません. この間言ったら驚かれたのですが, Haskellにもstripe-coreというStripe用のライブラリが存在します.

メールはGmailでまだ問題ない ユーザ登録時のメールやサポートにはG SuiteのGmailをnullmailer経由で使っています. サポートはGmailで来た問い合わせがGoogle Groupで閲覧出来るようになっています. 送信上限が存在するため, 大量にユーザ登録が来たら破綻しますが, 破綻したら嬉しい!

webpackの利用 あまりHaskellとは関係ないのですが, 一部の多少複雑な画面を制御するために, TypeScriptとReactを利用しています. TypeScriptはwebpackを利用してtemplates内でtsxをjsに変換しています. こうするとYesodでも問題なくTypeScriptが利用できます. shakespeareにも Text.TypeScript モジュールが存在するのですが, これはセットアップが面倒な上, エラーメッセージがわかりにくいので採用しませんでした. それでは変数やルーティングをどう渡しているのかと言うと, hamlet側に data-project=@{ProjectR projectId ProjectHomeR} のように要素の属性として埋め込んでいます. こういうことをしているとServantにしてJSON APIベースでシステムを組みたくなってきますが, こういう制御が必要なのは一部の画面だけなのでYesodの方が現状開発速度は高いです. また, CSSを多く書けるほど時間が取れないため, Bootstrapを利用しています. 4.0.0-alpha.5の頃からBootstrap v4を利用していました. Bootstrapの基本色設定などを変更するために, luciusではなくscssで統一したスタイルシートを使用して, Bootstrapをビルドしています. これらをビルドするための webpack.config.js は以下のようになっています. const ExtractTextPlugin = require ( "extract-text-webpack-plugin" ); module . exports = [{ entry : { "project-edit" : "./templates/project-edit.tsx" , "stripe-checkout-form" : "./templates/stripe-checkout-form.tsx" }, output : { filename : "[name].js" , path : __dirname + "/templates" }, resolve : { extensions : [ ".ts" , ".tsx" , ".js" , ".json" ] }, module : { rules : [{ test : /\.tsx?$/ , use : "awesome-typescript-loader" }]}, externals : { "js-cookie" : "Cookies" , "react" : "React" , "react-dom" : "ReactDOM" }, }, { entry : { "bootstrap" : "./static/bootstrap.scss" }, output : { filename : "[name].css" , path : __dirname + "/static" }, module : { rules : [{ test : /\.scss$/ , use : ExtractTextPlugin . extract ({ fallback : "style-loader" , use : "css-loader!sass-loader" }) }] }, plugins : [ new ExtractTextPlugin ( "[name].css" )] }]; また, ビルド前にwebpackでこれらをビルドするために, Setup.hs に以下のように書いてビルド前にhookをかけています. import Distribution.PackageDescription import Distribution.Simple import Distribution.Simple.Setup import System.Process main :: IO () main = defaultMainWithHooks simpleUserHooks { preBuild = preBuildSyakerake } preBuildSyakerake :: Args -> BuildFlags -> IO HookedBuildInfo preBuildSyakerake _ _ = callProcess "yarn" [ "run" , "build" ] >> return emptyHookedBuildInfo

テスト テストにはyesod-testの機構を使っています. JavaScriptを動かせるヘッドレスブラウザを動かすのはうまくいかなかったのでやめました. TypeScriptも型に守られているのでAPIが動くことだけテストしています.

コード管理 GitHubでコード管理して問題があればIssue建てて新しいコードはPull Requestで作って互いに問題がないことをコードレビューで確かめてMergeしています.

継続的インテグレーション Travis CIを使っています. .travis.yml を一部公開すると以下のようになっています. これでstack環境でテストが可能です. sudo : false cache : timeout : 1000 yarn : true directories : - $HOME/.local - $HOME/.stack language : node_js node_js : "8" services : postgresql addons : apt : packages : - libgmp-dev - libpam-cracklib - nullmailer before_install : - mkdir -p ~/.local/bin - export PATH=$PATH:$HOME/.local/bin - hash stack || travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack' - stack setup install : - stack --jobs 2 --no-terminal test --only-dependencies - stack install hlint - yarn - createuser -U postgres syakerake - createdb -U postgres -O syakerake syakerake_test script : - hlint src - stack --jobs 2 --no-terminal test

デプロイ Docker, Ansible, Chefと言ったものは使っていません. 依存ライブラリをインストールして, 単一のコードベースでEC2上で動くので, 過剰と判断しました. 一応インストール手順はメモしていますが, それぐらいです. git clone (次回からは git pull )して stack build して多少のセットアップをして sudo systemctl restart syakerake.service するという原始的な手段を取っています. Yesodのデプロイ手段として公式に推奨されているKeterがあります. Deploying your Webapp :: Yesod Web Framework Book- Version 1.4 これを私は使っていません. HTTPSの解決手段としてはLet’s Encryptとnginxを使っています. 一時期はKeterに移行することを考えていて, 未だにKeter移行用のブランチが残っています. しかし, 実際にステージング環境に投入してみたところ, Warpが直接2GBサイズのファイルを受け付けるとクラッシュすることが判明したため, 移行は凍結されました. Keterの利点は, サーバでビルドしなくて済むということです. しかし, EC2のインスタンスタイプを t2.medium にしていれば, 十分ビルドできるので無問題と判断しました. Herokuとか使っている人は頑張ってください. EC2上のディストリビューションがUbuntuになっているのに深い意味は無いです. Debian系でEC2が公式サポートしているのがUbuntuだっただけです. Amazon Linuxは辛すぎる. systemdを使わせてくれ, systemdは嫌いだけど好きなんです.