Clojure Web Development勉強 - Compojure (その1)
さて、Ringに続いてClojure Web Developmentでメジャーなライブラリ/フレームワークであるCompojureを使ってみる。
ちなみにここらへんも含めてCompojureに関しては@_ayato_pさんにいろいろご教授いただいた。あとウェブ開発の資料も大変参考にさせていただいている。
@zehnpaard 出来ます(笑)
— お金稼ぐのに飽きた (@_ayato_p) 2016年10月25日
@zehnpaard この話はこれで一度書いたんですよ。https://t.co/V8mcFDKFGN
— お金稼ぐのに飽きた (@_ayato_p) 2016年10月25日
多謝。
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
。ちなみにdefroutes
とGET
はマクロ、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を使った仕様で書いてみた:
前回handler.clj
と呼んでいたファイルをroutes.clj
とviews.clj
に分割。viewsにはリクエストマップを受け取り(not-foundの場合は引数なし)、レスポンスのbodyとなるテキストファイルを返す関数を集めてある。routesが今回追加のcompojureの部分で、ライブラリで定義されているdefroutes
、GET
、POST
、not-found
とviews.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文字列を直接コードに書き込まずに済むようにしたい。