LoginSignup
36
35

More than 5 years have passed since last update.

SPA開発における各層の実装例

Posted at

これの続き

ちなみに SPA は Single Page Application の略らしい。Smart Phone Application かと思ってた。

命名規則

View, Controller, Service, Model, API の各レイヤで作るサービスの名前のルール。

  • {layer}.{category}.{name}
    • layer
      • view, ctrl, srv, model, api のどれか
    • category
      • 省略可能
      • 必要に応じて「.」を増やし、階層化する
      • lowerCamelCase
    • name
      • サービス名
      • View, Service, Model は、一度生成されたものを使いまわすので、小文字始まり
      • Controller, API は、都度生成されるものなので 大文字始まり

また、ディレクトリは、サービス名の 「.」を「/」に変えるだけで行けるようにしたい。

以下は各層のサービスの例

View

views/loading.coffee
"use strict"
angular.module("app").factory "view.loading", [
  "$q", "$ionicLoading",
  ($q, $ionicLoading) ->
    return (stmt) ->
      $ionicLoading.show { template: "Loading..." }
      deferred = $q.defer()
      done = (x) ->
        deferred.resolve(x)
      done stmt done
      return deferred.promise.finally $ionicLoading.hide
]
  • $ionicLoading で、画面を暗くして操作を出来なくし、その間に処理を行う、という操作を実現出来る。
  • もともと各ページに書いてあったのを、view のサービスとして外出ししたもの。

Controller

controllers/product/gift/getForm.coffee
"use strict"
angular.module("app").controller "ctrl.product.gift.GetForm", [
  "$state", "$stateParams", "view.loading", "view.toaster", "srv.login", "srv.product", "srv.account", "srv.gift", "srv.message"
  ($state, $stateParams, loading, toaster, loginSrv, productSrv, accountSrv, giftSrv, messageSrv) ->
    loading (done) ->
      loginSrv.isLoggingIn().then (isLoggedIn) ->
        if not isLoggedIn
          return $state.go "user-login-form", {backToState: $state.current.name, stateParams: angular.toJson $stateParams }

        pProduct = productSrv.getProduct $stateParams.productId
        pFriendList = accountSrv.getFriendList()

        return $q.all [pProduct, pFriendList]
        .then ([product, friendList]) ->
          $scope.product = product
          $scope.friendList = friendList
          $scope.continue = (friendId) ->
            giftSrv.validate product.id, friendId
            .then ->
              $state.go "product-gift-confirm", {productId: product.id, friendId: friendId}
            .catch (errorCode)
              toaster.add messageSrv.message errorCode
]
  • これで 23 行
  • ページ遷移のための簡単な if 分岐を除き、基本的には model からもらったデータを view($scope)に渡しているだけ。
  • promise を駆使しているので、「どの時点でどのデータを取得できているか」は見失わないようにしたい。

Service

services/gift.coffee
angular.module("app").factory "srv.gift", [
  "model.inventory", "model.account", "model.gift"
  (inventoryModel, accountModel, giftModel) ->
    giftSrv =
      validate: (productId, friendId) ->
        pHasProduct = inventoryModel.hasProduct productId
        pHasFriend = accountModel.hasFriend friendId
        return $q.all [pHasProduct, pHasFriend]
        .then ->
          giftModel.validate productId, friendId

      giftTo: (productId, friendId) ->
        @validate productId, friendId
        .then ->
          giftModel.giftTo productId, friendId
    return giftSrv
]
  • Model を組み合わせて、ビジネスロジックを実現している。
  • 各種エラーは、Model が生成しているので Service では何もしていない
    • 今回はたまたまそうだというだけで、Service が Error を生成してはいけないわけではない

Model

models/gift.coffee
angular.module("app").factory "model.gift", [
  "model.message", "api.gift.Validate", "api.gift.Post"
  (messageModel, ValidateGiftAPI, PostGiftAPI) ->
    giftModel =
      validate: (productId, friendId) ->
        api = new ValidateGiftAPI()
        return api.request productId, friendId
        .then (response) ->
          if not response.data.hasError
            return true
          switch response.data.error_code
            when "noThankYou" then $q.reject messageModel.warn.REFUSED_YOUR_GIFT
            when "haveAlreadyGot" then $q.reject messageModel.warn.FRIEND_HAVE_ALREADY_GOT

      giftTo: (productId, friendId) ->
        api = new PostGiftAPI()
        return api.request productId, friendId
]
  • ほぼ API を呼び出すだけ。
  • API が返すエラーを promise を reject する形で上の層へ伝えている。
  • API の通信エラーや 401エラー などは、各種 Model では記述せず、$httpProvider.intercepts に登録するべきだ。

API

apis/gift/validate.coffee
anguler.module("app").factory "api.gift.Validate", [
  "$http", "api.Base"
  ($http, BaseAPI) ->
    class Validate extends BaseAPI
      path = "/gift/validate"

      useSsl: -> true

      getPath: -> super path

      getParams: (productId, friendId) ->
        return super
          productId: productId
          friendId: friendId

      request: (productId, friendId) ->
        return $http.get @getPath(),
          params: @getParams productId, friendId

    return Validate
]
  • 一番単純。$http を使って API を叩くだけ。
  • 自動生成する仕組みを作ってもいいかもしれない。
  • BaseAPI に、URL の共通部分や、API の version, デフォルトで渡す Params などが置いてある。

その他

キャッシュ周り、セキュリティ周りはまだ勉強中。
かなり実践に近いコードを書いてみました。何かの参考になれば幸いです。

36
35
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
36
35