読者です 読者をやめる 読者になる 読者になる

ボクココ

熱海で開発するブログ

Heroku と Amazon Lambdaを連携して、バックグラウンドジョブを実現した

気にはなってたAmazon Lambda をようやく使えたのでシェア。

Heroku はご存知の通り、Web とは別のWorker プロセスを立ち上げようとすると、その分プランに応じて倍増する。バックグラウンドジョブがそこまでないシーンで、常にWorkerプロセスを立ち上げっぱなしにするのは非効率。必要な時に必要な時だけバックグラウンドジョブを実行できる環境がないのか、考えていた。そこで登場するのが Amazon Lambdaだった、って訳だ。Amazon Lambda の魅力は何と言っても便利なだけでなく、1 か月あたり 100 万の要求と最大 3.2M 秒のコンピューティング時間が無料である、という点にある。素晴らしい。

今回やりたいこと

CSVなどのデータをアップロードして、それらを一括でHeroku Postgres の DBにデータを入れたい。それらの処理は時間がかかるので、バックグラウンドで実行し、終了時にメールを送るようにする。

連携イメージ

Amazon Lambdaの使い方としてはS3のアップロード時のイベントをトリガとして、LambdaのNode.js側からHeroku Postgresに接続し、データを挿入する。ややトリッキーなやり方だ。以下に概要図を示す。ここで重要なポイントは、 Heroku の Webサーバーを一切介さずに処理を終了させている ところだ。

f:id:cevid_cpp:20150828212125p:plain

このシステム開発でのポイント

以下、気になる点だけ読んでもらえれば。ブログ記事分けようと思ったけど、一気に書く。

  • Amazon LambdaやAmazon Cognito でのIAMユーザー、グループ、ロール等の管理
  • Amazon Lambdaの開発、デバッグ及びデプロイ環境
  • Node.js でのasyncを用いた開発スタイル
  • Heroku PostgreにCSVなどの大量データを一括インポートする

上記について体験したことをまとめておく。

Amazon LambdaやAmazon Cognito でのIAMユーザー、グループ、ロール等の管理

これ、初めて AWS触る人にとっては色々面倒なことをしなければならない。でもこれをやらなければならないのは最初だけなので、なんとか辛抱して頑張って設定してほしい。

まず図の1だ。さくっとAWScsvjsonをアップロードする、と書いてあるが、これだけでも裏側のAWSの設定を色々やってあげる必要がある。これに関しては、以前書いた以下の記事を参照していただきたい。ちなみにPaperclipやCarrierwaveを利用してHerokuのRailsサーバーからファイルをS3に上げることはできるが、Herokuはこれを推奨しておらず、以下のようにJavaScriptから直接上げるやり方を推奨している。

サーバーレスでJavaScript だけで画像ファイルをアップロードする方法 | selfree

これでまずは指定バケットにファイルをアップロードすることができた。

それとは別に、Amazon Lambdaを利用する場合はこの実行権限などを設定する必要がある。また、Amazon Lambdaを利用する場合はAWS CLIを利用しないと開発効率が上がらないので、これも同時に入れてあげる必要がある。それに向けて、グループを追加してその中で追加したユーザーでAWS CLIを利用することがセキュリティに大事である。これに関しても書いてるだけで大量になってくるので、詳細はAmazon Lambdaのドキュメント(英語) の、16ページを参照していただきたい。 Amazon Lambdaを初めて触る方は、それ以降のサンプルの実行も合わせて読み進めることで理解が深まるだろう。

Amazon Lambdaの開発、デバッグ及びデプロイ環境

AWS CLIさえ動かせる環境になったら、後はスイスイ開発できる。

AWS CLIで仮のイベントを発生させたり、AWS CLIからNode.jsのソースコードを直接アップロードできるようになる。今回はソースのzip化 -> ソースのアップロード -> Lambdaファンクションの実行 を一つのコマンドでできるように、シェルを組んで開発した。

ローカルでコードを実行する必要なんてない。だって 1 か月あたり 100 万の要求と最大 3.2M 秒のコンピューティング時間が無料 なんだから。

以下はデプロイシェルのサンプル。 lambdaディレクトリ内に index.js とnode_modulesがある想定。カレントディレクトリはlambdaディレクトリがある。

cd lambda
zip -r ../zipfile .
cd ..
aws lambda update-function-code --function-name sample --zip-file fileb:///path/to/zipfile
rm -f zipfile.zip

# invoke function
aws lambda invoke --payload file://input.json --invocation-type RequestResponse --function-name sample --region ap-northeast-1 --log-type Tail   outputfile.txt
rm outputfile.txt

最後アウトプットファイルを削除している。ここにもログファイルの内容を出力してくれるんだけど、 Amazon LambdaはAmazon Cloud Watchに自動的にログを出してくれるので、基本そちらを見ながら開発した。つまり、このシェルを実行した後、Cloud Watchで実行結果を確認する、という流れを取った。これだけでだいぶ開発効率が上がった。

Error: Cannot find module 'index' 、というエラーでちょっとハマった。これはzipファイルを固めた時、その解凍した直下にそのjsファイルがないとこの問題が発生する。zip化する時に要注意。

Node.js でのasyncを用いた開発スタイル

先ほど紹介したAmazon Lambdaのドキュメントを読んでみると、S3のイメージリサイズの所で、Asyncを利用したサンプルが紹介されている。これを利用すると、Node.jsにありがちなコールバック地獄から抜け出すことができる。特に今回は PostgreSQLのNode.jsドライバーである node-postgres を利用するが、これはクエリを投げるたびにコールバックを返すので、asyncを使わないととんでもないことになる。ぜひ使おう。

ここで、ちょっと癖っ毛のあるasyncのwaterfallの使い方について注意点だけ書いておく。基本的な構文は以下の通り。

async.waterfall([
    function first(callback) {
        getSomething(options, function (err, result) {
          if (err) {
            callback(new Error("failed getting something:" + err.message));
          } else {
            callback(null, result);
          }
        });
    },
    function second(result, callback) {
        // ...
    }
], function (err, result) {
   if (err) { console.error(err);}
   console.log("Fin.");
});

最初の関数にcallbackという次に渡すための関数がデフォルトで入る。次の処理に移る時、first内でこのcallbackを呼んであげる。そしたら次のsecondに処理が移る。ここで重要なのが、 callbackの第一引数はerrオブジェクトが入るという点。つまり、問題なく次に進みたい時は、callbackの第一引数はnullをセットしてあげなければならない!これを知らずに普通に第一引数にデータ入れると、なぜか処理がぶっ飛んでいきなりFin.と表示されてしまう。ただ、ほとんどのライブラリのコールバック呼び出しの第一引数はerrが入る慣習?っぽいので自分で呼ぶ時だけ要注意なのかも。

また、次のsecondに引数を渡したい時は callbackの第二引数以降に渡すデータは次の関数の第一引数以降に変わる。今回のソースだとresultの所。これは引数何個でも同じ。secondの最後の引数にcallbackを追加してあげて、second内でまたcallbackを呼んであげるようにする。

以上の点にさえ気をつければ問題なく開発できるかと思う。

Heroku PostgreにCSVなどの大量データを一括インポートする

まず、Amazon LambdaからHeroku Postgresに直接接続することは可能。Heroku Postgresの URL postgres://user:pass@ec35232-52528.compute-1.amazonaws.com:5432/database?ssl=trueを接続してあげる。ここで、?ssl=trueを末尾につけないと接続できないので注意。

それ以降はnode-postgresのクエリをそのまま発行してあげればOK。実行結果のコールバックの第一引数はerr、第二引数にresultということで、特に気にせずasyncのコールバックを渡せる。以下のように。

client.query("select * from users;", callback);

次のwaterfall の関数で (result, callback) の引数でresultを取ってこれる。規約に従ってれば、asyncは便利!

さて、一つずつデータを挿入するなら、シンプルにinsert文を入れればいいだけだが、掲題のように、CSVファイルで一括インポート、という時はちょっとテクニックがいる。今回の想定として、シンプルなマスターデータではなく、インクリメンタルなIDや外部キーなどのテーブルに入れたいという場合を想定する。その場合、以下のような手順を踏む。

  1. CSVファイルをlambda内の/tmp内に保存する。
  2. CSVファイルと同じ構成のダミーテーブルを作成する。
  3. INSERT INTO users ... SELECT FROM dummy_users WHERE ... の構文でデータを挿入
  4. ダミーテーブルを削除
  5. /tmpのファイルを削除

このやり方は、以下の記事を参考にさせていただいている。

PostgreSQL の COPY コマンドと SQL だけで様々なデータをインポートする – hoge256 blog

/tmp内に移動させたcsvファイルを消さなければならないことに注意!次実行する時、同じ名前でファイルがあった場合、lambdaが別ユーザーで実行した時にpermission deniedとなってしまうことがあるからだ。ここら辺はLambdaがリソース使いまわしている点があるのでよくわからない挙動をすることがある。

Heroku Postgresはadmin権限で実行することができないため、csvファイルを直接COPY FROMを実行させることは不可能だった。ということで、標準入力でファイルを読み取ってそれをデータとして渡してあげる必要がある。それを実現するのに、node-pg-copy-streamsを利用した。waterfall 内で以下のような感じになる。ここの書き方にハマった。。

          function copyTable(result, callback) {
            console.log("Copy CSV to table.");
            var stream = client.query(copyFrom("COPY " + tempTableName +" FROM STDIN USING DELIMITERS ',' CSV;"));
            var fileStream = fs.createReadStream("/tmp/data.csv");
            fileStream.on('error', function() {
              callback("Open Stream Error");
            });
            fileStream.pipe(stream).on('finish', function() {
              callback();
            }).on('error', function(err) {
              callback(err);
            });
          }

まとめ

本来ならもっと一つ一つを丁寧に説明すべきかとは思うが、この記事で最も言いたかったことは、Heroku と Lambdaを連携してバックグラウンドジョブを実行させることは可能で、このやり方はなかなかお勧めできる、ということだ。

EC2は高い。だからWebサーバーはHerokuを使う。が、Herokuはバックグラウンドジョブをする場合には余計に課金が発生する。だからAmazon Lambdaでそれらの処理を任せる。これは両方の良い所を取った、素晴らしい構成だと思う。

この構成を考えていた人にとって有益な記事となれば幸いだ。

最も気に入っているTEDの紹介

今日は久々に技術以外のエントリー。最近めっちゃ寒くなってきて、夏終わっちゃったのかな〜。夏好きなのに。

さて本題。

優れたリーダーはどうやって行動を促すか

私の最も気に入っているTEDはこれだ。有名だから一度は聞いたことがあるかもしれない。

何が素晴らしいって、この成功している人たちの共通点をあまりにもシンプルに説明しているところだ。初めて聴いたときはこの説得力になるほど〜とうなってしまった。

これで特に参考になるのは、キャッチコピーを考える際であろう。

キャッチコピーを考える

キャッチコピーはそのサービスを印象付ける重要な要素だ。これでその製品のイメージが決まると言ってもいいほどだ。これについて本気で考えている人たちは、2日間まるまるそのワンフレーズのために考え抜いて決める、とも言われている。

そこでこの動画を元にすると、その製品の特徴を伝えるだけでは、心に響くキャッチはできないということになる。それは What や How、どのような製品かを説明しているにすぎない。その先にある、製品を使ったことで、使った人がどうなっていくと開発者は信じているのか。なぜあなたはこのプロダクトに全力を注いでやっているのか。その点を必死に考えて言語化することが必要になる。その言葉や表現に人は初めて共感し、そのプロダクトが他のと違う何かを感じて購入したい、やってみたいと思うようになるということだ。

始めのアーリーアダプターと呼ばれるそうは、共感で動く。そしてその共感を他の人にも伝えたいと考えている。彼らに響くメッセージがまずは重要で、そしたらだんだんとキャズムを超えて広く製品が知れ渡るようになる、という訳だ。

このブログを書き続ける理由

このブログのキャッチも考えてみるか。「技術について詳しく書いています。」じゃあ誰の心にも響かないから NG。これを読んだ人がどう思い、どういう行動をしてほしいかをまずは考えてみた。

「自分と同じ問題や考えを持った人が必ず日本のどこかいて、このブログを通して彼らの手助けができると信じているから。」となった。このブログのサブキャッチは、、っとそんなすぐに思いついたら誰も苦労しないよなw。

「熱海で開発するブログ」とか全くメッセージがこもってない。まぁそもそもこのブログでそこまで本気で考える必要もないかな、と思ってるので、気が向いたらサブキャッチを変えてみよう。

さぁ、思い切って"IE未対応"を公表しよう

今更ながら、"IE"である。これは常々Webプログラマーを悩ませ続けたブラウザである。このブラウザだけ不思議な挙動を起こし、そのための対応に時間を取られる。まさにweb開発の負の側面であり、誰もが嫌う作業だ。そこにクリエイティビティなどない。

はて、なぜこんなにも不評なIEをみんな対応し続けなければならないのだろう? それは日本人の IE利用率が高いからに他ならない。

f:id:cevid_cpp:20150825233436p:plain

これを見てみると、IEが40%ものシェアがある。2位がChromeの32%。だがしかし、世界を見るとChromeが圧倒的な一位がほとんどである。

なぜこんな状況なのか?

理由は 日本のWebエンジニアがIE対応をしているから だと考える。上司に言われて嫌々ながらも対応してしまう。その対応をみんながするものだから、使っているユーザーがIEに不便を感じず、使い続けてしまう。だからIEのシェアが下がらずに、いつまでもIEの対応をしなければならなくなる。。

まさに負の連鎖だ。IE対応が嫌だと言いながら、その原因を作っているのはその対応をしている人たちというのは何とも皮肉な話である。

さぁ、思い切って"IEは未対応"を掲げよう! IE対応に手間暇かけている間に、HTML5を使ったクールな技術でユーザー体験を向上させよう。IE対応なんて数年後には全くいらなくなるが、最新技術を使った開発はその先数年後でも有用な機能になるはずだ。

皆が一斉にやればこの問題は一瞬で片付く。ユーザーはサービスがIEに対応していないから仕方なく他のブラウザを入れる。そしたらそのスピードや便利さにハマってもうIEに戻ることなんてないだろう。それほどには他のブラウザは優秀なのは皆知っているはずだ。

Webエンジニアのムーブベントに期待している。

Amazon Lambda の使い時について翻訳メモ

AWS

今作のAWSサービスの中でもっとも注目されているサービスの中の一つである、Amazon Lambdaについて。

これをいつ使うべきかについては公式ドキュメントにがっつり書いてあったので引用しよう。

When should I use AWS Lambda?

AWS Lambdaをいつ使いべきか?

  • The Amazon Elastic Compute Cloud (Amazon EC2) web service offers flexibility and a wide range of EC2 instance types to choose from. It gives you the option to customize operating systems, network and security settings, and the entire software stack, but you are responsible for provisioning capacity, monitoring fleet health and performance, and using Availability Zones for fault tolerance. EC2は高度な柔軟性と幅広いインスタンスタイプの選択を可能とする。OSやネットワーク、セキュリティ設定やソフトウェア選択のカスタマイズ性を得られるが、パフォーマンス監視やフォールトトレランスのための仕組みを自分で用意する必要がある。

  • Elastic Beanstalk offers an easy-to-use service for deploying and scaling applications onto Amazon EC2 in which you retain ownership and full control over the underlying EC2 instances. Elastic BeanstalkはEC2のオーナー権限とフルアクセスを持ちつつアプリケーションのデプロイとスケーリングを簡単にする方法を提供する。

AWS Lambda is a great alternative to using these other AWS compute services if you can write your application code in languages supported by AWS Lambda, and run within the standard runtime environment and resources provided by the service. AWS Lambdaは、もしあなたが特定のサポートされた言語のコード(Node.js or Java)を書くことができるなら、その他の AWSのEC2やBeanstalkなどの素晴らしい代替サービスとなる。AWS Lambda上で提供される実行環境とリソースの上で動くアプリケーションを実行することができる。

The higher-level abstraction that AWS Lambda offers is the convenience of your being responsible only for your code. AWS Lambda manages the compute fleet that offers a balance of memory, CPU, network and other resources.This is in exchange for flexibility, which means you cannot log in to compute instances, customize the operating system or language runtime.These constraints enable AWS Lambda to perform operational and administrative activities on your behalf, including provisioning capacity, monitoring fleet health, applying security patches, deploying your code, running a web service front end, and monitoring and logging your functions あなたはコードにだけ集中することができる。AWS Lambdaがメモリのバランス、CPU,ネットワークやその他のリソースを管理してくれる。柔軟性の代わりあなたは実行環境にログインすることはできないし、OSや言語の実行環境をカスタマイズすることはできない。これらの制約がAWS Lambdaの監視やセキュリティパッチ、デプロイ、ログ処理などを可能にしている。

これを調べるにあたった経緯

最近 Amazon API Gateway っていうもうひとつ注目すべきサービスが出てきて、Lambdaと組み合わせてサーバー立てずにサービス作ろうって流れがある。その一例がjaws-stack/JAWS · GitHubだ。

私のそもそものLambdaのイメージが、"AWS内のイベントが起きた時にそれがトリガとなって実行できるもの"という認識だった。そのためWebアプリサーバーとしてのLambdaの使い方は本来の使い方とはちょっと違うのではないのかな?と思ったというわけである。

結局のところ、Lambdaは色んな使い方できるよとしか書いてなかったので、新しいWebアプリ作成のためにLambdaを使うのは全然アリのようである。

私のRails と jQuery のフロントエンド開発指針

Railsでの基本的な開発スタイルといえば、ページはリンクとフォーム送信、そしてリダイレクトの基本構成だろう。確かにこれでWebアプリケーションを作ることができる。

ただ今回はよりリッチなWebアプリケーション、具体的にはAjaxを駆使した開発について、Railsでどうやって開発していけばグチャグチャにならずに簡潔に書けるのか、私が心がけている点を紹介したいと思う。これを読めば、別にクールなJavaScriptフレームワークを使わずとも、シンプルなjQueryで作れることを知ることができるだろう。

そもそもなぜリッチにするのか

リッチなWebアプリケーションにすれば、以下のような点のメリットがある。

  • 毎回application.js や application.css、共通画像などを読み込む必要がなくなるため、サーバー負荷に優しい。
  • ユーザーはページ遷移を意識せずにWebアプリを利用できるため、UXが高まる。クールなWebアプリが作れる。

リッチWebアプリはユーザーにとっても開発者にとっても利点がある。しかし、このメリットの背景には以下のようなデメリットがある。

上記デメリットを以下に簡潔に書くか。それをRails上でどのように実現するのか。今回はそこに焦点を当ててノウハウをまとめる。

まずはコントローラ毎にコンポーネントを分ける

これめっちゃ大事。そして全体の基本になる話だ。例えば、 UsersControllerを作成した場合、Railsでは users.css.scss, users.js, users/index.html.erb などが関連ファイルとして生成されることだろう。まずは以下のように<div>もしくは<section>タグで区切りをつけよう。

//scss
#userCmp {
  ...
}

// js
$(function() {
  $("#userCmp..").click(function() {
    ....
  })
});

// html
<div id="userCmp">
  ...
</div>

プログラミングの世界ではネームスペースとも言える概念だが、これをすると他のコントローラの影響を考えずにuserCmpに集中してソースを書くことができる。これをやらないと、なぞのイベントが発生したり、CSSがグチャグチャになったりする。

htmlを返すアクション、jsonだけを返すアクションなどを使い分ける

Ajaxというと、どうもJSONだけでやりとりする、という考えを持っておられる方も多いようだが、別にhtmlをAjaxのレスポンスにすることも可能だ。これを知っておくだけでかなり開発の幅は広がる。例えば、グローバルタブがあって、Ajaxでレスポンスをhtmlにし、そのhtmlをcontentの中身として書き換えてみよう。

//html
<ul class="tab">
  <li data="/items">Tab1</li>
  <li data="/carts">Tab2</li>
</ul>
<div id="contents"></div>

// js
var ajaxHtml = function(url) {
  $.ajax({
    type: "GET",
    url: url,
    success: function(content){
      $("#contents").html(content);
    }
  });
}
$(".tab li").click(function() {
  var url = $(this).attr("data");
  ajaxHtml(url);
});

// controller
def index
  render layout: false
end

// index.html
<div id="itemCmp">
  ....
</div>

これがリッチWebアプリのベースとなる部分だ。ItemsController側も同様に itemCmpを作成し、css, js はそれぞれitemCmpで予め限定しておこう。これだけでRailsのTurboLinksもどきの機能を実装できる(戻る機能を除く )。ちなみにタブの移動などは共通で呼び出すAjaxとなるので、本当ならちゃんとレスポンスコードによるエラー処理を書くべき。

場合によってはJSONを返すパターンももちろんあっていい。その際にHTMLをどこかに挿入したい、といった場合に全部jQueryでDOMを構築するのは超絶面倒なので、以下のようにdisplay: noneなテンプレートを用意してそれに当てはめるのがグッド。

<div id="template">
   <!-- 複雑なHTML -->
   <span id="name"></span>
</div>

var $template = $("#template").clone();
$template.find("#name").html(res.name);
$("#contents").content($template);

cloneしないと連続で読んだ時にtemplateが置き換わったグチャグチャのHTMLを再度取ってきてしまうので、この呼び出しが必要だ。

コンテンツ書き換え後のJavaScript処理に対応する

さて、ここまででタブによってAjaxでコンテンツを切り替える、というテクニックを見てきた。リッチアプリではコンテンツを切り替えた後のHTML要素もJavaScriptで動くように実装しなければならない。ただここで普通に $("#mybtn").click(..)と書くだけでは、ロード時にこの要素は無い要素なので正しく動作しない。イベントに関してはonを利用して以下のように書く必要がある。

//items.js
$(document).on("click", "#itemCmp .element", function() {
  // code
});

これで動的に読み込んだ要素にもjQueryのイベントを発火させることができるようになる。

ここでさらなる問題が出てくる。それは"Ajaxで取ってきたコンテンツの初期処理をしたい"というパターン。RailsAjaxでhtmlを返してすぐにJavaScriptを実行したい場合だ。その場合、assets/javascripts/内に

$(function() {
  // 初期処理...
});

と書く方法は正しく動作しない。すでにドキュメントはページ全体で読み込まれた後にAjax呼び出しをするからだ。これを正しく動作するようにするには、Ajaxで取ってきたHTML内にscriptタグを書いて動作させる必要がある。

<div id="itemCmp">
  ....
</div>
<script>
$("#itemCmp .item").append(...);
</script>

再喝だが、このパターンを使わなければならないのはAjaxでHTMLを呼び出して呼び出し後にすぐJavaScriptを実行したい時だけ、ここに書くべきである。イベントの処理はここではなく、 $(document).onにしてassets内に書くべきだ。なぜかというと、ご存知のとおりRailsはassets内のJSはプリコンパイルによってメソッド名が書き換えられるので、そうした共通処理はこのHTML内で呼び出すことができなくなるためである。

Railsの変数をJavaScriptで取得したい

JavaScriptを書いていると、Railsの変数の値をJavaScriptで使いたい、というパターンが頻出する。これに対応する最も簡単な方法は、"display: none"なスタイルで変数をまとめたHTMLを書くことだ。

<div class="hidden">
  <span id="user_id"><%= @current_user.id %></span>
  <span id="status"><%= @current_user.status %></span>
</div>

//js
parseInt($("#user_id").text());

ただ、この書き方で注意しなければなら無いのは、悪意のあるユーザーがこのHTMLの内容を書き換えた後にJavaScriptを実行させる、ということもできてしまう点にある。そのためRails側にリクエストを投げる時は必ずそのリクエスト内容が正しいかどうか検証するコードをお忘れなく。

HTMLが汚れて醜い、という方には、RailsとJSを共存させるためのGem gon があるので調べてみるといいだろう。

HistoryAPIで戻る処理を実現する

ここまでで、サーバーとAjax通信をしてインタラクティブなページをjQueryだけで作成することができるようになった。最後の関門は "ブラウザバック"をJavaScriptに対応させることだ。これにはHTML5からのHistoryAPIを利用する。ていっても最近はHistoryJSがあるので、これで楽々だ。

HistoryAPIの基本的な考え方としては、Historyスタックに履歴をどんどんプッシュしていく感じだ。プッシュされた場合や、戻るボタンを押された場合には、共通のイベント(statechange)が呼ばれる。この時にAjaxを呼び出してその状態を表示するような実装を行う。

  // pushStateや戻るボタンが押された時に呼ばれる
  History.Adapter.bind(window,'statechange',function(){
    var State = History.getState();
    ajaxHtml("/" + State.data.action);
  });

// ページ移動時
History.pushState({action: "items"}, "Items Title", "?" + "items");

pushState メソッドは3つの引数を持つ。一つ目がその状態でのデータ(JSON)、二つ目がタイトル、三つ目が遷移後のURLだ。 データの中にAjaxで置き換えるべきurl情報を格納しておけば、戻る時にそのデータを読み込んでAjaxを送ってHTMLを書き換えることで、まるでページが戻った可能ような挙動を実現できる。遷移のたびにURLを変えたい場合は第三引数を正しくしていしてあげることで、たとえばそのリンクをブックマークしてまた来た時に、その状態をAjaxで予め表示しておくことができるようになる。

終わりに

今回はRails x jQuery で特に重要なAjax処理について詳しく説明した。AngularJSを使えば確かにこれらリッチなアプリを作るには簡単に実現できることはわかるが、自分で一から作る分にはjQueryで作った方がハマる確率は圧倒的に減る。というかDOM操作とAjaxだけなのでハマる要素がない。Railsで搭載されているTurbolinksに関してもそうだ。あれも仕組みをしっかり理解していないとハマりやすいが、jQueryで構築すればその心配もないというわけだ。

Rails は標準で jQuery を搭載してくれているが、Railsはこのような開発のためにコントローラごとにjsやcssを分けてくれているわけだ。その設計指針通りに開発すれば、依然としてRailsによる圧倒的開発効率をリッチWebアプリでも実現できる、と私は考えている。

長文に付き合っていただき、ありがとうございました。

テストコードの書きすぎが変化を嫌うようになる

テストコードを書くエンジニアはクール。そんな風潮が出回っている。

f:id:cevid_cpp:20150810222304p:plain

テストコードを書くことは非常に重要で、同意できる。自分も単体テストレベルのテストコードはテストが書きやすいし変化しにくい箇所なので要所要所で書くようにしている。

緑で埋め尽くされたテスト結果は何度見ても気持ちの良いものだ。テストコードによってコーディングに楽しさが加わる。テストは多ければ多いほどいい。1000個以上テストケースあって全部通ってるシステムはイケてるだろ?

本当にそうか?

テストを書きすぎる、特によりView層に近い部分のテストコードをたくさん書くと、画面や処理の変更のたびにそれを書き直す必要が出てくる。

これが多少の変更だったらまだいいんだけども、ガラッと作りを変えた時にテストコードもそれに合わせて何倍も時間をかけて対応しなければならない。

まさにこの問題は品質とスピードのトレードオフなんだけども、テストを書きすぎてしまった場合に大規模な変更をしたくなくなるような開発環境にしてしまうと、それはエンジニアとしてダメなところ。まさに自分がコントローラ層のコードを書きすぎて泣く羽目になった経験からそう感じた。本当に優秀なエンジニアはコントローラ層の変更にも耐えうるソースコードを書いているのかなぁ。

テストは自分以外でも誰かにお願いできる部分であるので、全てをコードでカバーしようと思うと逆に非効率な場合もある。

程よく大事なところはしっかりテストを書く、ということが大事なんじゃないかなと思う。

RedisはRDBの次に学ぶべきDBかもしれない

ざっくりとではあるが、以下の本を一読した。

Redis入門 インメモリKVSによる高速データ管理

Redis入門 インメモリKVSによる高速データ管理

日本語でRedisについて詳しく書かれた本はこの本くらいしかないかと思うが、それでも次のレベルを目指すWebエンジニアにはお勧めしたい本であった。

ただ、注意していただきたいのは本書は全く入門向けではないということ。確かに前半はコマンド一覧のような説明はあるが、後半からはがっつりと実戦的な内容が書かれている。本書はRedisの説明ではなく、Redisを使って一般的な問題をどう解決するか、ということに主眼が置かれている。

そういった意味で私も一回読んだだけでは到底全てを理解することはできなかった。時間をかけて他の文献も参考にしながらもう一度じっくり読みたいと考えている。

近年のサービスはRedis主体で作られる

もっとも有名な例はLINEだ。あれはペタバイトクラスのRedisを立ててメッセージをやりとりしている。本書にはチャットシステムの構築のヒントも書かれており、メッセージングシステムを構築するのにRedisが優れている点が説明されていた。

そのほかにも、転職システムやTwitterタイムライン、広告配信システム、ログ集計など最近のスタートアップが好んで扱う分野についてサンプルコードを含んで丁寧に説明されていた。それらスタートアップのほとんどは本書を参考にシステムを作ったのではないか、とも感じた。インフラの技術が進めば作れるサービスも変わる、ということを実感できた。そしてRedisが登場したからこそ今までできなかった大規模データ処理サービスが実現できるようになったのだとも言える。

RedisはSQLを書き換える?

Redisは本当にいろいろなことができると知った。今まではちょっと高機能なKVS(Key-Value Store)くらいにしか思っていなかったのだけど、そんなことはなくリレーショナルデータベース(RDB)並みの機能をRedisだけで実現できてしまうのだ!

ただReidsだけで全てを作るのはおすすめできない。結局はKVSの拡張であって設計が大変難しいのと、実際に後からメンテナンスするときもRedisだけで作ったシステムはとても複雑なものになるからだ。例えばキー名を"user:124"にして、中身をハッシュにする。といった設計でやっていく訳だがこれだけでもちょっとトリッキーな感じがしてしまう。

ではどういうときにRedisを使うべきなのか。それは高負荷がかかりそうな部分を補助するときに使う、という立ち位置だと考える。 大規模データを素早く処理したいときにRedisは威力を発揮する。

てことでそこまで負荷のかからない初期のスタートアップはぶっちゃけRDBだけでなんとかなると思う。それでサービスがうまくいったときに負荷のかかる処理をRedisに移行する、というやり方でいいような気もする。

それくらいRedisでデータ設計をするのは難しく失敗しやすい。

だからこそ、RDBの次に学ぶべきものとしてのRedisの立ち位置がもっとも良いのではないか、と思った。

現在のRedisの利用

今運用しているCallConnectでは、Redisを利用している。といってもログインセッションをRedisに置いているくらいだ。ただこの用途だけでもCookieに保存していたRailsのセッションをセキュアに管理できるようになるので大変有用だと考えている。今後バックグラウンドジョブのキューイング処理にまたRedisを利用したいと考えている。

それと同時に今後負荷のかかりそうなデータ処理を扱う際にはRedisの利用を検討していく。