6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

現場のSails.js #2 Controllerのアーキテクチャ編

Last updated at Posted at 2015-12-01

概要

弊社では一部の社内プロジェクトでSails.jsを利用しています。その中で得られたベストプラクティスやらバッドプラクティスをまとめていこうと思います。
第2回はControllerの作り方について。

動作環境

  • node.js v0.12.7
  • Sails.js v0.11.2
  • sails-mysql v0.11.0

アーキテクチャ概要

View(ejs) -> ViewController -> ApiController -> Service -> Modelの5層構造になっています。ViewとModelは生のSails.js、ServiceもよくあるService層なので、ここではViewControllerとApiControllerについて詳しく説明します。

ViewControllerとApiController

Sails.jsでは、View(html)を返すかjsonを返すかという違いを、たった1行のコードで表現できます。

HogeController.ts
class HogeController {

  // hogepageをレンダリングして返すaction
  find(req, res) {
    Hoge.find().then(hoges => {
      res.view('hogepage', hoges);
    });
  }
  
  // hogesのjsonを返すaction
  findOne(req, res) {
    Hoge.find().then(hoges => {
      res.send(hoges);
    });
  }
}

export default new HogeController();

この仕組みはサクッとコードを書くには便利なのですが、規模が大きくなってくると混乱が生じます。Viewを返すactionとjsonを返すactionが入り混じり、どのControllerにどのメソッドがあるのか、把握することが難しくなってしまいます。そのため、ViewControllerとApiControllerを明確に分離しています。

ディレクトリ構成は以下のような形です。API用HogeControllerのパスが api/controllers/api/HogeController.tsapiが続くのが若干気持ち悪いですが、この構成ならSails.jsのblueprint機能でGET /api/hoge/find のようなURLが自動的に紐付くので結構便利です。

/api
 |_/controllers
   |_HogeController.ts
   |_/api
     |_HogeController.ts

また、ViewControllerは直接Service層にアクセスすることはなく、ApiController経由で全てのデータを取得します。

API Controllerのサンプル

APIControllerは直接APIとして叩くケースと、ViewControllerからアクセスされるケースがあります。ただ、この違いはViewControllerの方で吸収しているため、APIControllerでは普通に res.ok でsuccessのレスポンス、 res.send でエラーレスポンスを返しているだけです。

api/controllers/api/TagController.ts
class TagController {
  'use strict';

  constructor() { }

  find(req, res) {
    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);
    });
  }

View Controllerのサンプル

ViewControllerからApiControllerにアクセスするために、expressのresオブジェクトと同じI/Fのオブジェクトを渡して結果を受け取っています。(ちょっと作りは雑ですが)TypeScriptでコードを書いているので、ベースクラスを作成して共通化しています。

api/controller/BaseController.ts
export default class BaseController {
  Api(method, req) {
    return new Promise((resolve, reject) => {
      method(req, {
        ok: (result) => {
          resolve(result);
        },
        send: (error, result) => {
          if (typeof error === 'object') {
            return resolve(result);
          }

          reject(new NetworkError('API call Error', error));
        }
      });
    });
  }

実際のViewControllerは以下のように実装します。先ほどのBaseControllerを継承し、ApiControllerのメソッドをApi()メソッドを利用して呼び出しています。

api/controller/TagController.ts
import BaseController from './BaseController';
import ApiTagController from './api/TagController';

class TagController extends BaseController {

  find(req, res) {
    sails.log.verbose('view::TagController#find');
    sails.log.verbose('params', req.allParams());
    sails.log.verbose(`user=${req.user ? req.user.id : "null"}`);

    this.Api(ApiTagController.all, req).then(tags => {

      res.view('tagpage', tags);

    }).catch(error => {
      // error
    });
  }
}

export default new TagController();

まとめ

ViewControllerとApiControllerを明確に役割を分けることで、可読性が上がりメンテナンス性が向上します。何でも出来るフレームワークというものは破綻させることも簡単なので、適切なルールを作って運用していきましょう。

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?