Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
5
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

@KeitaMoromizato

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

概要

弊社では一部の社内プロジェクトで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を明確に役割を分けることで、可読性が上がりメンテナンス性が向上します。何でも出来るフレームワークというものは破綻させることも簡単なので、適切なルールを作って運用していきましょう。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
5
Help us understand the problem. What are the problem?