このページの本文へ

2017年はNode.jsの達人になる!いま知っておきたいベストプラクティス10

2017年02月13日 08時09分更新

文●Azat Mardan

  • この記事をはてなブックマークに追加
本文印刷

前回の記事『10 Tips to Become a Better Node Developer in 2017(達人に学ぶ、優れたNode使いにレベルアップするための10のヒント)』では、いますぐ使える10個のNode.jsのヒントやテクニックを紹介しました。この記事もそれに続いて10個のベストプラクティスを紹介し、Nodeのスキルを次のレベルに上げられるようにしたいと思います。

以下が今回紹介する内容です。

  1. npmスクリプトを使う:bashスクリプトを使わず、npmとNodeのスクリプトを使ったほうが管理しやすい(例:npm run buildstarttest)。Nodeの開発者が新プロジェクトに取り組むには、npmスクリプトこそが唯一の真実
  2. 環境変数(process.env)を使うprocess.env.NODE_ENVを、development(開発)あるいはproduction(本番)にして活用する。フレームワークによってはこの変数を使うので、慣例通りの使い方をすすめる
  3. イベントループを理解するsetImmediate()は名前と裏腹に「すぐに(immediate)」ではなく、nextTick()は「次(next)」ではない。CPUを酷使する次のイベントループに対してはsetImmediate()あるいはsetTimeout()を使用する
  4. 関数パターンによる継承を使う:プロトタイプ(クラス)パターンによる継承の理解とデバッグで苦悩したり無駄な議論になるのを避けるため、Nodeのコントリビューターが用いているように、関数パターンによる継承を使う
  5. 名前を付けるなら適切に:名前自体が、なにかを説明してくれるように意味のある名称を使う。またファイル名に大文字を使わない。もし分かりやすくしたければ代わりにダッシュ(-)を使う。ファイル名の大文字は見た目の問題だけでなくプラットホームの互換問題がある
  6. 正規のJavaScriptを使わないのもひとつの手:ES6・ES7は、より良いJavaScriptを謳ったCoffeeScriptがすでに登場したあとで、6年もの歳月を費やして制定された悲運のバージョン。変数の種類(var/const/let)、セミコロン(;)、class、ほかの引数のことなどで無駄に議論せずに早くデプロイしたいなら、CoffeeScriptを使う方法もある
  7. ネイティブコードを使う:トランスパイラー(変換コンパイラ)を使う際は、ビルド不要で使えるようにネイティブJavaScriptコード(コンパイルの結果生成されたもの)もコミット(アップロード)する
  8. ミドルウェアExpressを知るnpm i compression -Sを使うことと、ロギングの正しい実装。環境に依存しすぎず、かつ軽視しすぎない。npm i morgan -S
  9. スケールアップ:Nodeによる開発ははじめからクラスター化し、かつステートレスなサービスの使用を考える。pm2あるいはStrongLoopのクラスター管理を使う
  10. リクエストのキャッシュ:nginx、あるいはリクエストレベルのキャッシュであるVarnishキャッシュやCDNキャッシュのような静的なファイルサーバーを盾にして、Nodeサーバーを最大限に活用する

それでは、それぞれを分けて個別に説明してきましょう。準備はいいですか?

npmスクリプトを使う

アプリのビルド、テスト、そしてなによりアプリの開始には、npmのスクリプトを作成するのが当たり前になりました。これはNodeの開発者が新プロジェクトに取り掛かるはじめの一歩です。人によっては(例:1234)Grunt、Gulp、類似のツールを捨てて、より下層にある(そしてもっと信頼できる)npmスクリプトを選ぶことさえあります。彼らの言い分は分かります。npmスクリプトは実行の前後にフックがあるので、自動化をさらに進められるのです。

"scripts": {
  "preinstall": "node prepare.js",
  "postintall": "node clean.js",
  "build": "webpack",
  "postbuild": "node index.js",
  "postversion": "npm publish"
}

フロントエンドの開発ではしばしば、コードのリビルドのため複数の監視プロセスが欲しいときがあります。たとえば、1つがwebpack、もう1つがnodemonといった具合です。&&(直列処理)を使えば、最初のコマンドはプロンプトをリリースしないので実現できます。しかし、複数のプロセスを生成して同時に走らせるconcurrentlyという便利なモジュールがあります。

また、webpack、nodemon、Gulp、Mochaといった開発者用のコマンドラインツールはコンフリクト(衝突)を避けるためにローカルで使います。たとえば./node_modules/.bin/mochaを指定するか、bash/zshプロファイルに以下のコマンドを加えてパスを通します。

export PATH="./node_modules/.bin:$PATH"

環境変数を使う

情報の漏洩を防ぎ、かつ正しくコーディングするために、プロジェクト初期から環境変数を使ってください。さらに言えば、ライブラリやフレームワークによっては(たとえば、Expressが該当します)、そのふるまいを変更するための情報をNODE_ENVのような変数で受け取ります。これをproductionに設定してください。MONGO_URIAPI_KEYの値もセットします。シェルファイル(例:start.sh)を作成して.gitignoreに加えます。

NODE_ENV=production MONGO_URL=mongo://localhost:27017/accounts API_KEY=lolz nodemon index.js

またnodemonには環境変数を入れるための設定ファイルがあります(こちらの例を参照)。

{
  "env": {
    "NODE_ENV": "production",
    "MONGO_URL": "mongo://localhost:27017/accounts"
  }
}

イベントループを理解する

Nodeがこれほどに速くて賢い理由、それは強力で巧妙なイベントループにあります。もしこれがなければ、無駄に入出力の完了を待つはめになります。つまりNodeの入出力の管理は高度に最適化されているのです。

もしCPU負荷の大きなタスク(例:高度な計算、パスワードのハッシュ計算、ファイル圧縮など)が必要なとき、単にこうしたCPUタスクの新規プロセスを生成するだけでなく、コールバックにあるコードを次のループに継続させるsetImmediate()setTimeout()により時間を延ばすことを検討します。nextTick()は、その名とは裏腹に同じサイクル内で動作します。なんていうことでしょう!

下の写真が、イベントループに取り組んでいるBert Belderが作った図解です。さすがに彼は良く分かっていますね!

関数パターンによる継承を使う

JavaScriptはオブジェクトがほかのオブジェクトを継承するときプロトタイプによる継承をサポートしています。ES6になりclass演算子も加えられました。しかし、関数パターンによる継承と比べて複雑です。ほとんどのNodeの達人はシンプルな関数パターンを好んでいます。シンプルなファクトリーパターンで書け、prototypenewthisを使わなくてよいのです。また各オブジェクトがメソッドのコピーを持つので、プロトタイプを更新(そのインスタンスすべてに変更が及ぶ)しても、その影響はありません。

さらに何十個ものNodeモジュールを製作した多作の天才、TJ Holowaychukのコードを見てください。Expressでは関数パターンによる継承を使用しています(全ソースコード)。

exports = module.exports = createApplication;
// ...
function createApplication() {
  var app = function(req, res, next) {
    app.handle(req, res, next);
  };

  mixin(app, EventEmitter.prototype, false);
  mixin(app, proto, false);

  app.request = { __proto__: req, app: app };
  app.response = { __proto__: res, app: app };
  app.init();
  return app;
}

オブジェクト指向化のため、Nodeモジュールのコアではプロトタイプ継承が数多く使われています。もしプロトタイプ継承パターンを使うなら、どう動くか分かっていることが大切です。「JavaScript inheritance patterns」でさらに詳細を閲覧できます。

名前を付けるなら適切に

これは明らかです。良い名称は説明にもなります。下の例はどうでしょう?

const dexter = require('morgan')
// ...
app.use(dexter('dev')) // When is the next season?

これではapp.use()だけを見てもdexterがなにをするのか分かりません。もっと意味のある名前loggerにしたらどうでしょうか。

const logger = require('morgan')
// ...
app.use(logger('dev')) // Aha!

同じように、ファイル名はその中のコードの役割を正しく表したものでなければいけません。Node(GitHub)のプラットホーム付属コアモジュールすべてを収めたlibフォルダーを見れば、知らない人が見てもファイル名やモジュール名が明確です。

events.js
fs.js
http.js
https.js
module.js
net.js
os.js
path.js
process.js
punycode.js
querystring.js

ちょうどメソッドや変数のように、内部モジュールにはアンダースコア(_)が付いています(_debugger.js_http_agent.js_http_client.js)。開発者は「これは内部インターフェイスであり、もし使用するなら変更・削除されても文句を言わずに自己責任で」ということが見て分かるのです。

正規のJavaScriptを使わないのもひとつの手

この意味が分かりますか? そう、その通りなのです。ES6、ES7(ES2016)で追加された2つの機能を加えても、まだJavaScriptには独自のクセがあります。正規のJavaScriptのほかに、ほんの少しの準備であなたのチームに恩恵をもたらす選択肢があるのです。習熟の度合いやアプリの特性次第では、強い型付けを持つTypeScriptFlowを使うほうが良いかもしれません。あるいは逆に考えて、純粋に関数型のElmClojureScriptもあります。またCoffeeScriptも、歴戦を生き延びた強力な選択肢です。さらにDart 2.0を検討してもいいでしょう。

ただ少しマクロを使いたい(マクロなら好みの言語を作れますし)だけで、新しい言語までは導入したくないなら、Sweet.jsを検討してください。「コードを自動生成するためのコードを書く」という、まさにしたかったことができます。

もしJavaScript以外の道を行くにしても、コンパイル後のコード(素のJavaScript)も含めるようにしてください。ほかの開発者がその言語をよく知らず、ビルドできないかもしれないからです。たとえばVS Code(Angular 2に次ぐ、TypeScriptで最大のプロジェクトの1つ)は、型を用いたNodeモジュールのパッチ作成にTypeScriptを使用しています。VS Codeリポジトリvscode/src/vs/base/node/の中には、cryptoprocessといった見慣れた名前が並んでいますが、その拡張子はts(TypeScript)です。リポジトリにはほかにもtsファイルがあります。しかし同時に、素のJavaScriptのコードもvscode/buildとして取り込まれています。

ミドルウェアExpressを知る

Expressは良くできていて、熟成されたフレームワークです。その良さとは、ふるまいを設定できる多数のモジュール群です。ですから、もっともよく使われているこのミドルウェアを知り、どのように使うかを学んでください。「my Express cheat sheet」を利用すると良いでしょう。私もリスト中のミドルウェアモジュールを使っています。たとえばnpm i compression -Sはレスポンスを遅くしてダウンロード速度を遅らせます。logger('tiny')またはlogger('common')は、それぞれ少ない(開発用)あるいは多い(本番用)、ログ収拾のモジュールです。

スケールアップ

NodeはノンブロッキングI/Oによる非同期通信に優れている上に、あくまでも1スレッドだけなので非同期のコーディングでもシンプルです。早い段階、それもコードの最初の1行目から、スケール変更がしやすいのです。コアclusterモジュールを使えば垂直方向のスケーリングが比較的容易にできます。しかし、さらに良い方法はpm2あるいはStrongLoopクラスターコントロールといったツールを使用することです。

pm2を使い始める方法の例です。

npm i -g pm2

pm2を開始したら同じサーバーの4つのインスタンスをスタートします。

pm2 start server.js -i 4

Dockerを使うなら、pm2のバージョン2以降にはpm2-dockerコマンドがあります。Dockerfileは次のようになります。

# ...

RUN npm install pm2 -g

CMD ["pm2-docker", "app.js"]

公式のAlpine Linux pm2のイメージはDocker Hubにあります。

リクエストのキャッシュ

リクエストのキャッシュは、Nodeのインスタンス(先に書いたとおりpm2や類似ツールを使えば1つ以上得られます)を最大限に利用できる、DevOps(開発と運用の共同)の優れた手法です。するべきことは、Nodeサーバーが、リクエストしたりデータ処理したりビジネスロジックを実行したりという本来のアプリ処理をできるようにしてやり、静的なファイルへのトラフィックを別のサーバー(たとえばApache httpdやNginx)に移すことです。ここでもDockerを使うのが良いでしょう。

FROM nginx

COPY nginx.conf /etc/nginx/nginx.conf

私は複数のコンテナ(nginx、Node、Redis、MongoDB)を相互に関連づけて構成するためにもDockerを次のように使っています。

web:
  build: ./app
  volumes:
    - "./app:/src/app"
  ports:
    - "3030:3000"
  links:
    - "db:redis"
  command: pm2-docker app/server.js

nginx:
  restart: always
  build: ./nginx/
  ports:
    - "80:80"
  volumes:
    - /www/public
  volumes_from:
    - web
  links:
    - web:web

db:
  image: redis

最後に

オープンソースの現代で、オープン化されて自由に使える、信頼と実績のツールを学ばない理由はありません。使い始めるために、関係者になる必要はありません。学びには終わりはありません。確実に言えるのは、今後経験する試行錯誤により、別なベストプラクティスが生まれるだろうということです。それは確実です。

締めくくりに、ソフトウェアがいかに世界中に広がっているか、そのソフトウェアの世界でJavaScriptがいかに広がっているかを書こうと考えていました。1年ごとの新標準のリリース、増え続けるnpmモジュール、ツール、カンファレンス…。しかしそれはやめて、警鐘を鳴らして終わりたいと思います。

多くの人が新しい次世代フレームワークや言語を追いかけているのを目にします。一種の「光るモノを追う」シンドロームです。毎週のように新しいライブラリーを学習し、毎月のように新しいフレームワークを学んでいるのです。彼らは取り憑かれたようにTwitter、Reddit、Hacker News、JS Weeklyをチェックしています。膨大な活動量をJavaScriptの世界に投じて、やるべきことを先送りしているだけなのです。彼らのGitHubの公開履歴は空っぽなことでしょう。

新しいものを学ぶのは良いことですが、実際になにかを築き上げることと混同してはいけません。大事なことは、そして自分の報酬の元になっているものは、実際になにかを作るということです。技術のみに没頭しすぎるのはやめましょう。次世代Facebookを作っているわけではないのですから。Promisesか、ジェネレーターか、それともasync/awaitかは、どうでもよいのです。なぜならそのような議論で誰かが返答してきたときには、私はすでにコールバック関数を書き終えているでしょう(素のES5/6/7の倍速でコードを書くためにCoffeeScriptを使用しています!)。

最終的にベストな方法は、定評のある手段を使うことです。一番良いのは土台となる技術をマスターすることです。ソースコードを読み、コードの中で新しいことを試し、そして一番大切なのは自分で山のようにコードを書くことです。ここで読むのをやめて、書くべきコードを書いて納品しましょう!

念のため、記事の内容だけでは足りない場合は、Nodeに関する良い記事を以下にリストにしておきます。

※「Node.jsの達人の教え:10個のコツ」は、ゲストライターのAzat Mardanによるものです。SitePointのゲスト投稿では、Webコミュニティの著名な執筆者や講演者の魅力的なコンテンツの提供を目指しています。

(原文:10 Node.js Best Practices: Enlightenment from the Node Gurus

[翻訳:西尾健史/編集:Livit

Web Professionalトップへ

WebProfessional 新着記事