Arantium Maestum

プログラミング、囲碁、読書の話題

Clojure Web Development勉強 - Compojure (その1)

さて、Ringに続いてClojure Web Developmentでメジャーなライブラリ/フレームワークであるCompojureを使ってみる。

ちなみにここらへんも含めてCompojureに関しては@_ayato_pさんにいろいろご教授いただいた。あとウェブ開発の資料も大変参考にさせていただいている。

多謝。

Compojureはたまにフレームワークとして紹介されているが、現時点ではただのライブラリだと思う。とにかく責務がかなり限られている。が、限られているようで微妙に複数の異なることをやっていて、それが個人的にはちょっと鼻につく。

githubのドキュメントの最小例を見てみるとこんな感じになっている:

(ns hello-world.core
  (:require [compojure.core :refer :all]
            [compojure.route :as route]))

(defroutes app
  (GET "/" [] "<h1>Hello World</h1>")
  (route/not-found "<h1>Page not found</h1>"))

こんな小さな例なんだから:refer :allじゃなくて[defroutes GET]してほしいとか思ったり思わなかったり。とにかくこのGETやらroute/not-foundやらで「requestがこんなパターンにマッチしたらこういうresponseを返す」というロジックを作り、それらをまるでcondのようにつなげているのがdefroutes。ちなみにdefroutesGETはマクロ、route/not-foundは関数。

今まで書いてきたようにringでこれを実現するとしたら:

(ns hello-world.core
  (:require
    [ring.util.response :as res]))

(defn app [req]
  (cond
    (= (:uri req) "/")  (-> (res/response "<h1>Hello World</h1>") (res/content-type "text/html"))
    :else (-> (res/not-found "<h1>Page not found</h1>") (res/content-type "text/html"))))

という形になるので、確かにCompojureを使うと綺麗になっていて書きやすい。

気をつけないといけないのは、GETが同時に二つのことをしている点で、

  • uri(以外にもparameterなどでもできる)でパターンマッチングをしている
  • ring.util.responseのように文字列を装飾してレスポンスマップの形式に変換している

と、一粒で二度美味しいとみるか、責務が違うものが同じマクロで同時に展開されていてちょっと分かり難いとみるか。

個人的には、ここにさらにuriとparameterのdestructuringでローカル変数束縛の機能が入ると、ちょっと異なる責務が同じマクロに集中しすぎている印象を持ってしまう。

それを踏まえて、前回のコードをcompojureを使った仕様で書いてみた:

gist.github.com

前回handler.cljと呼んでいたファイルをroutes.cljviews.cljに分割。viewsにはリクエストマップを受け取り(not-foundの場合は引数なし)、レスポンスのbodyとなるテキストファイルを返す関数を集めてある。routesが今回追加のcompojureの部分で、ライブラリで定義されているdefroutesGETPOSTnot-foundviews.cljの関数を使って最終的なringハンドラ関数appを作っている。

これをcore.cljでmiddlewareに包んでringのjetty adapterに渡しているのは従来の通り。

現在の悩みどころとしては、views.cljのほぼ全ての関数をreqをとってresを返すというシグネチャに統一しているデザインでいいかどうか。特に、display-resultの引数をより分割されたものにして、ルーティングのGETマクロが持つdestructuring機能をより有効に活用すべきかどうか。

(defn display-result [req]
  (let [{:keys [params uri]} req
        param-name (get params "name")
        req-type (if (= uri "/get-submit") "GET" "POST")]
    (str
     "<div>
        <h1>Hello " param-name "!</h1>
        <p>Submitted via a " req-type " request.</p>
        <p><a href='..'>Return to main page</p>
      </div>")))

見ての通り、現在だとdestructuringはこのviewの関数の中で全てやっている。個人的にはこっちの方がいい気がするのだが、いろいろ書いているうちに意見が変わるのかもしれない。

次回はHiccupというClojure特有のHTML Templateライブラリを使って、HTML文字列を直接コードに書き込まずに済むようにしたい。