Node.jsの話し

外村 和仁(ピクセルグリッド)

2011.11.12

Sugamo.css

あじぇんだ

  • CodeGridの構成とか
  • ルーティングとか
  • DBとモデルとか
  • connectとか
  • フロー制御とか
  • テストとか

CodeGridの構成とか

アプリの設計で気を付けたこと

  • ある程度規模が大きくなってもメンテできるように
  • 規模というのはアクセス数じゃなくてアプリの大きさ
  • expressの欠点は自由すぎるところ
  • できるだけルールを決めてファイルを分割

ディレクトリ構成

.
├── app.backend.js
├── app.frontend.js
└── lib
    └── codegrid
        ├── index.js
        ├── config
        │   ├── config.js
        │   ├── config.local.js
        │   ├── index.js
        │   └── test.js
        ├── controller
        │   ├── backend
        │   │   ├── root.js
        │   │   ├── entry.js
        │   │   ├── user.js
        │   │   └── etc...
        │   ├── frontend
        │   │   ├── root.js
        │   │   ├── entry.js
        │   │   ├── user.js
        │   │   └── etc...
        │   └── index.js
        ├── model
        │   ├── index.js
        │   ├── models
        │   │   ├── entry.js
        │   │   ├── user.js
        │   │   └── etc...
        │   └── validator.js
        ├── setting
        │   ├── backend.js
        │   ├── frontend.js
        │   └── index.js
        ├── test.js
        └── utils.js

ルーティングとか

解決したい問題

  • ルーティングのマップ一覧が一目で見れるようにしたい
  • ファイルを分割したい

方法1

ルーティングマップを書いてパースする

https://gist.github.com/1354601

var routesMap = {
    'GET  /'      : 'root:index'
  , 'GET  /about' : 'about:index'
  , 'GET  /entry' : 'entry:index'
  , 'POST /entry' : 'entry:create'
};

方法2

  • 特定ディレクトリのファイルを全部読み込む
  • 今回採用した方法
  • ルーティング一覧が見れないのスクリプトで対応した

DBとモデルとか

MongoDBという選択

  • 単純に使ってみたかっただけ
  • node.jsとの相性はいい
  • スケーラブルで大きいデータを扱うのが得意だけど今回は関係ない
  • データ構造が柔軟なところ、JSでクエリが書けるところが利点
  • 今のところ困るのはJOINがないのとトランザクションがないことくらい

Mongooseという選択

  • たぶん一番使われてるMongoDBのORM
  • LearnBoost謹製
  • RDB(SQL)ほどORMの必要性は感じない
  • けどある程度大きくなったらやっぱあったほうがいい気も

connectとか

connectとはなんぞや

  • middleware framework
  • リクエストの処理に様々なmiddlewareを差し込める
  • expressはconnectをベースにしてつくられてる

通常のHTTP Server

var http = require('http');

http.createServer(function(req, res) {
  res.end('hoge');
}).listen(3000);

connectを使った場合

var connect = require('connect');

var app = connect.createServer();
app.use(function(req, res, next) {
  if (req.url === '/') {
    next();
  }
  else {
    res.end('not found');
  }
});

app.use(function(req, res, next) {
  res.end('hoge');
});

app.listen(3000);

様々なmiddleware

  • bodyParser
  • static
  • vhost
  • basicAuth
  • logger
  • csrf
  • cookieParser
  • session
  • compiler
  • methodOverride
  • responseTime
  • router
  • staticCache
  • directory
  • favicon
  • limit
  • profiler
  • query
  • errorHandler

expressから使う場合

var express = require('express');
var app = express.createServer();

app.use(express.methodOverride());
app.use(express.bodyParser());
app.use(express.static(__dirname + '/public'));

app.get('/', function(req, res) {
  ...
});

CSSとJS

  • connectのmiddlewareでlessをコンパイルして返す
  • jsは複数ファイルをつなげて一ファイルにして返す
  • 本番ではメモリキャッシュするので速度的にはたぶん問題ない

フロー制御とか

これよめばおk

http://www.slideshare.net/koichik/node1

二つの非同期処理

  • イベントリスナ
  • コールバック

イベントリスナ

var ret = asyncFunc();

ret.on('data', function() {
  ...
});

ret.on('hoge', function() {
  ...
});

ret.on('error', function() {
  ...
});

コールバック

var fs = require('fs');

fs.readFile('./hoge.html', 'utf8', function(err, file) {
  console.log(file);
});
  • callback関数を受け取る関数は、引数の最後にcallback関数を受け取る
  • callback関数の最初の引数はエラ−(なければnull)
  • たまにそうなってないのもある

二つの違いとか

  • 通知すべきイベント1つで一回しか発生しないものはcallback、そうでないならEventEmitterというのが普通かも?
  • 両方対応してるやつも
  • EventEmitterの場合は置いておいて、callbackの問題を扱う

Node.jsで困ること

var fs = require('fs');
var path = './hoge.txt';

fs.writeFile(path, "hello", function (err) {
  if (err) return console.error(err);  
  fs.open(path, "a", 0644, function (err, file) {
    if (err) return console.error(err);  
    fs.write(file, " world", null, "utf-8", function (err) {
      if (err) return console.error(err);  
      fs.close(file, function (err) {
        if (err) return console.error(err);  
        fs.readFile(path, "utf-8", function (err, data) {
          if (err) return console.error(err);  
          console.log(data);
        });
      });
    });
  });
});

Deferredを使った場合

var fs = require('fs');
var Deferred = require('jsdeferred').Deferred;
var path = "./hoge.txt";

Deferred.next(function() {
  var deferred = new Deferred();
  fs.writeFile(path, "hello", function(err) {
    if (err) return deferred.fail(err);    
    deferred.call();
  });
  return deferred;
})
.next(function() {
  var deferred = new Deferred();
  fs.open(path, "a", 0666, function(err, file) {
    if (err) return deferred.fail(err);    
    deferred.call(file);
  });
  return deferred;
})
.next(function(file) {
  var deferred = new Deferred();
  fs.write(file, " world", null, "utf-8", function(err) {
    if (err) return deferred.fail(err);    
    deferred.call(file);
  });
  return deferred;
})
.next(function(file) {
  var deferred = new Deferred();
  fs.close(file, function(err) {
    if (err) return deferred.fail(err);    
    deferred.call();
  });
  return deferred;
})
.next(function() {
  fs.readFile(path, "utf-8", function(err, data) {
    if (err) return deferred.fail(err);    
    console.log(data);
  });
})
.error(function(err) {
  console.error(err);
});

様々なフローコントロールモジュール

https://github.com/joyent/node/wiki/modules#wiki-async-flow

chain-tiny

  • Flow controllの仕組みを理解したくて書いてみた
  • 個人的には気に入ってる

chain-tinyで書いてみる

var fs = require('fs');
var chain = require('chain-tiny');
var path = "./hoge.txt";

chain(function(next) {
  fs.writeFile(path, "hello", next);
})
.chain(function(next) {
  fs.open(path, "a", 0644, next);
})
.chain(function(file, next) {
  fs.write(file, " world", null, "utf-8", function(err) {
    next(err, file);
  });
})
.chain(function(file, next) {
  fs.close(file, next);
})
.chain(function(next) {
  fs.readFile(path, "utf-8", next);
})
.end(function(err, data) {
  if (err) console.error(err);
  console.log(data);
});

テストとか

  • nodeunitでテスト書いてる
  • モデル中心にコントローラーのテストも正常なレスポンスが返るか程度はやってる
  • ホントは昨日の夜Jenkiins入れてCIのデモしたかったかけど拡張つくるのに時間もっていかれた
  • のでTravis CIの紹介

おわり