概要
弊社では一部の社内プロジェクトでSails.jsを利用しています。その中で得られたベストプラクティスやらバッドプラクティスをまとめていこうと思います。
第1回はバックエンド側のAltJSの活用から。
動作環境
- node.js v0.12.7
- Sails.js v0.11.2
- [npm]sails-hook-babel v5.0.1
- [npm]typescript v1.5.3
- [npm]grunt-tsconfig-update v0.0.1
AltJSの活用
Sails.jsはnode.jsのフレームワークなので実際の開発ではAltJSを使用しています。TypeScriptで書いたコードを --target es6
に変換後、babel
でES5に変換しています。
TypeScript
採用理由
個人的にTypeScriptが好きなのでバックエンド側コードは全面的にTypeScriptを採用しています。TypeScriptを導入するメリットは大きいと考えていて、モジュール間の整合性を厳密に定義出来る点が気に入っています。次回以降でアーキテクチャについては詳しく説明しますが、弊社ではViewController -> ApiController -> Service -> Modelという4層構造でバックエンドを構築しています。各層のAPIを変更したときに、デグレが発生していないかをコンパイル段階で気付けるのは良いですね。
設定方法
$ npm i typescript grunt-tsconfig-update grunt-shell --save-dev
Sails.jsにはGruntが組み込まれているのでこれを活用することにします。とは言えあまり依存したくもないので、tsconfig.jsonを出力するだけのモジュール grunt-tsconfig-update
と、 grunt-shell
で直接tsc
を叩く構成にしています。今見ると改善したくなってきた。。
module.exports = function(grunt) {
grunt.config.set('tsconfig', {
main: {}
});
grunt.loadNpmTasks('grunt-tsconfig-update');
};
module.exports = function(grunt) {
grunt.config.set('shell', {
typescript: {
command: "npm run ts"
}
});
grunt.loadNpmTasks('grunt-shell');
};
module.exports = function (grunt) {
grunt.registerTask('build-server', [
'tsconfig:main',
'shell:typescript'
]);
};
"scripts": {
"ts": "./node_modules/typescript/bin/tsc -p ./"
}
あとはtsconfigを用意して終わりです。
{
"compileOnSave": false,
"compilerOptions": {
"target": "es6",
"declaration": false,
"sourceMap": false,
"removeComments": true,
"noLib": false,
"preserveConstEnums": false,
"moduleResolution": "node"
},
"filesGlob": [
"./api/**/*.ts",
"!./**/*.d.ts",
"!./node_modules/"
],
"files": [
]
}
コンパイル
$ grunt build-server
Babel
採用理由
最初に書いた通り、TypeScriptは--target es6
で使っています。これはいつでもTypeScriptを捨てられるようにするためです。個人的にはずっとTypeScriptで書きたいのですが、書きたくない人に引き継ぐ可能性があるので。。
設定方法
sails-hook-babel
というnpmを1つ入れるだけで終わりです。これでSails起動時に自動的にbabelで変換してくれます。ただ、このnpmは今のところbabel6対応をしていないため、長期メンテする予定のシステムでは使わない方が良いかもしれません。いつもどおり返還前のコードを置くディレクトリを用意して、普通にbabelを叩いてapi/*
に出力するほうが良いのかも。
$ npm i sails-hook-babel --save
使用しているd.tsファイル
Sails用の型定義ファイルが無かったので自前で作って使っています。ただ公開出来るレベルでは書いていないので。。型定義ファイルにどれだけ作成コストをかけるのかというのは常に悩む。
/// <reference path="./typings/bundle.d.ts" />
declare var sails: Sails.sails;
declare module "sails" {
import * as express from 'express';
module s {
interface Request extends express.Request {
port: number;
user: User;
session: Session;
sessionID: string;
allParams(): Object;
logout();
file(name: String): any; // TODO file object
}
interface Response extends express.Response {
ok(params: Object | number);
view(view: string, params?: Object);
json(params: Object);
}
interface Session {
regenerate(callback: (error) => void);
}
interface User {
id: number;
}
}
export = s;
}
declare module Sails {
interface sails {
log: log;
}
interface log {
verbose(...data: any[]);
error(...data: any[]);
warn(...data: any[]);
debug(...data: any[]);
info(...data: any[]);
silly(...data: any[]);
}
}
コードサンプル
実際のコードの抜粋です。雰囲気は伝わるかなと。
/// <reference path="../../../sails.d.ts" />
/// <reference path="../../../typings/bundle.d.ts" />
import * as Sails from 'sails';
class TagController {
'use strict';
constructor() { }
find(req: Sails.Request, res: Sails.Response) {
sails.log.verbose('api::TagController#find');
sails.log.verbose('params', req.allParams());
sails.log.verbose(`user=${(req.user ? req.user.id : 'null') }`);
const page = +req.param('page') || 1;
TagService.find({
limit: 30,
page: page
}).then((tags) => {
res.ok(tags);
}).catch((error) => {
sails.log.error("api::TagController.find", error);
res.send(500);
});
}
}
export default new TagController();
まとめ
TypeScript良いよね。