Heroku で Clojure アプリを Noir を使ってデプロイしてみよう

k2nr さんのブログにある Compojure を利用した例を参考にさせていただき、Web フレームワークとして Noir を使用したデプロイ方法について記述します。

Compojure との比較が目的なので、同じく 「ぐちってー」(noir-guchitter) を作りました。

完成したものは以下においておきます。



目的

  • Noir を使った Web アプリの作成方法を学ぶ。
  • Web フレームワークとして Compojure を使った場合との違いを確認する。
  • Heroku へのデプロイ方法を再確認する。(前にやったが忘れてしまったので。)



登場人物の説明

まずは、HerokuでClojureアプリをCompojureを使ってデプロイしてみよう その1 を参照してください。不足分について説明しておきます。


Noir

Clojure の Web フレームワークです。最近、ちょくちょく見かけるようになりました。内部的には Ring や Compojure を使ってて、それらに対して便利なマクロ(defpage, defpartial) を追加した Web フレームワークといったところでしょうか。
leinigen で Noir のプロジェクトを作成すると雛形となるディレクトリ構成を作成してくれるので、どこにファイルを配置すればいいかが分かりやすいかと思います。


Twitter Bootstrap

CSS フレームワークです。Web デザインがあまり得意でないエンジニアが簡単にそれっぽい?
デザインのサイトを作る際に利用します。


Noir のインストール

leiningen のプラグインとして Noir をインストールします。(leinigen については他サイトで。)


$ lein plugin install lein-noir 1.2.1


プロジェクトの作成

以下のコマンドで noir を使用したプロジェクトの雛形が作成されます。


$ lein noir new noir-guchitter

作られる雛形のディレクトリ構成はこんな感じです。


$ tree noir-guchitter

noir-guchitter
├── README.md
├── project.clj
├── resources
│   └── public
│   ├── css
│   │   └── reset.css
│   ├── img
│   └── js
├── src
│   └── noir_guchitter
│   ├── models
│   ├── server.clj
│   └── views
│   ├── common.clj
│   └── welcome.clj
└── test
└── noir_guchitter

11 directories, 6 files



コード確認

最終的に完成したコードについて Compojure 使用時との違う部分を中心にざっくり説明します。


project.clj
(defproject noir-guchitter "0.1.0-SNAPSHOT"
  :description "FIXME: write this!"
  :dependencies [[org.clojure/clojure "1.3.0"]
                 [noir "1.2.1"]
                 [postgresql "9.1-901.jdbc4"]
                 [org.clojure/java.jdbc "0.1.1"]
                 ]
  :main noir-guchitter.server)

使用しているライブラリが違うので":dependencies"で指定するライブラリが違っているのと、":main"で指定している名前空間が違うくらいです。


server.clj (src/noir_guchitter/server.clj)
(ns noir-guchitter.server
  (:require [noir.server :as server]))

(server/load-views "src/noir_guchitter/views/")

(defn -main [& m]
  (let [mode (keyword (or (first m) :dev))
        port (Integer. (get (System/getenv) "PORT" "8080"))]
    (server/start port {:mode mode
                        :ns 'noir-guchitter})))

Compojure 使用時の core.clj に対応するファイルです。同じくこのファイルがアプリの核になります。
Compojure 使用時に記述していた"ルーティング"の記述は、"src/noir_guchitter/view/"以下の view ファイルに記述するため、server.clj にはルーティングの記述はありません。


models (src/noir_guchitter/models/[guchi.clj, db.clj, migration.clj])

名前空間が(guchitter --> noir-guchitter)に変更されている以外、Compojure 使用時と同じです。


guchi.clj (src/noir_guchitter/views/guchi.clj)
(ns noir-guchitter.views.guchi
  (:require [noir-guchitter.views.common :as common]
            [clojure.string :as string]
            [ring.util.response :as ring]
            [noir-guchitter.models.guchi :as model])
  (:use [noir.core :only [defpage]]
        [hiccup.core :only [html h]]
        [hiccup.page-helpers :only [doctype]]
        [hiccup.form-helpers :only [form-to label text-area submit-button]]))

(defn guchi-form []
  [:div {:id "guchi-form" :class "hero-unit"}
   [:form {:method "POST" :action "/"}
    (label "guchi" "愚痴ってもいいんだよ?")
    [:textarea {:name "guchi" :id "guchi" :class "span8" :rows "4"}]
    [:input {:class "btn btn-primary btn-large btn-guchiru" :type "submit" :value "ぐちる"}]]])

(defn display-guchi [guchis]
  [:div {:id "guchi"}
   (map
    (fn [guchi] [:h2 {:class "guchi-unit"} (h (:body guchi))])
    guchis)])

(defn create [params]
  (let [guchi (:guchi params)]
    (when-not (string/blank? guchi)
      (model/create guchi)))
  (ring/redirect "/"))


(defpage "/" []
  (common/layout "ぐちってー"
                 (guchi-form)
                 (display-guchi (model/get-all))))

(defpage [:post "/"] {:as params}
  (create params))

Noir でも HTML のテンプレートエンジンとして Hiccup を利用しているため、"defpage"の記述以外(defnで定義している部分)は Compojure 使用時と同じです。
Noir では基本的には、"defpage"マクロを使って View の中にルーティングの処理を記述します。Rails とかで MVC フレームワークを使っていた人は、コントローラは? どこいったと思うかもしれませんが、Noir ではプロジェクト作成時に models, views のディレクトリは作成されるのですが、 controllers ディレクトリは作成されません。

これについては Noir の作者が質問に答えているので興味がある方は一読してみてください。



common.clj (src/noir-guchitter/views/common.clj)
(ns noir-guchitter.views.common
  (:use [noir.core :only [defpartial]]
        [hiccup.page-helpers :only [include-css html5]]))

(defpartial layout [title & content]
  (html5
   [:head
    [:meta {:charset "utf-8"}]
    [:title "noir-guchitter"]
    (include-css "/css/reset.css"
                 "/css/bootstrap.css"
                 "/css/guchi.css")]
   [:body
    [:div {:class "container"}
     [:div {:id "header" :class "page-header"}
      [:h1 title]]
     [:div {:id "content"} content]]]))

Compojure 側の layout.clj に対応し、views で共通に利用するlayout を定義しています。layout の定義には"defpartial"マクロを使っています。"defpartial"マクロは、Hiccupの"html"関数をラップし、名前をつけて呼び出せる形に関数を作成しているといったところでしょうか。


実際に動かしてみる

Compojure 使用時と基本同じです。違う点は、

  • ローカル起動時にPORT指定が不要。(デフォルトで"8080"を利用するように server.clj に記述)
  • migration 指定時の名前空間として"noir-guchitter.models.migration"を指定。


くらいです。


Heroku へのデプロイ

こちらも Compojure 使用時と基本同じです。違う点は、

  • Procfile に指定する名前空間として"noir-guchitter.server"を指定。
  • 共用DBにテーブルを作成する際の名前空間として"noir-guchitter.models.migration"を指定。

くらいです。なんか動かないぞ?と思ったら名前空間が間違っていないか確認してみてください。


まとめ

個人的には、Compojure より Noir の方が取っ付き易いかと思いました。
記事を書いた直後は、Compojure とそんなに変わらない?と思いましたがそれは、k2nr さんが作成(設計)した Compojure アプリと比較しているからかと後々気づきました。

実際、Compojure を使って開発をする際には、最初


$ lein new guchitter

として、プロジェクトを作成しますが、この時作成されるディレクトリ構成はこんな風になります。


guchitter
├── README
├── project.clj
├── src
│   └── guchitter
│   └── core.clj
└── test
└── guchitter
└── test
└── core.clj

Compojure 使用の guchitter にあった、views, models, controllers といった枠組みはプロジェクト作成時につくられないので自分で設計する必要があります。

Noir には他にも Session や Validation を扱う関数などがあり慣れてしまえば割とサクサク開発できるようになるかなと思いました。