Part1 Webアプリケーションはじめの一歩
これからClojureで初めてのWebアプリケーションを作成していきましょう。作っていく上であまりにも普段使わなかったり、見ないようなものだとどういう機能が必要か想像もつかないと思いますので、このシリーズではブログアプリ作っていくことにします。ブログアプリというとログイン機能があって、記事の一覧が見れて、記事を作成、編集、削除することができれば、世の中にあるブログと遜色ないようなものが出来そうです。また実際に記事をデータベースに保存したり、アクセスされたURLでページを振り分けたり、HTMLをレンダリングしたり、投稿された記事がおかしくないかバリデーションしたりと、どのようなWebアプリケーションを作ったとしても必要とされることが多いものを実装できるため、実際のプロダクトなどでも活かすことができるでしょう。 今回作るブログアプリの簡単な機能要件は以下の通りです。
記事の一覧を閲覧することができる
記事の詳細を閲覧することができる
記事の一覧をタイトルで検索して絞り込みすることができる
ログインしたユーザーは記事を書いて保存することができる
ログインしたユーザーは記事を編集して保存することができる
ログインしたユーザーは記事を削除できる
それでは早速プロジェクトを作成していきましょう。適当なディレクトリで以下のコマンドを実行します。
code: (shell)
$ lein new app cljblog
作成されたディレクトリを確認してみると以下のように、プロジェクトの雛形が展開されていることが分かります。
code: (shell)
$ tree
.
├── CHANGELOG.md
├── doc
│ └── intro.md
├── LICENSE
├── project.clj
├── README.md
├── resources
├── src
│ └── cljblog
│ └── core.clj
└── test
└── cljblog
└── core_test.clj
次に作成されたディレクトリの下で次のコマンドを実行してみます。
code: (shell)
$ lein run
Hello, World!
「Hello, world!」とターミナルに表示されたら成功です。この段階ではターミナルに文字列を表示しているだけなので、次はブラウザに「Hello, world!」と表示させてみます。
REPLからサーバーを起動する
早速ブログアプリ(cljblog)に手を加えていきます。まずはproject.cljを開いて以下のようにring/ring-coreとring/ring-jetty-adapterをプロジェクトの依存ライブラリとして追加します。
code: project.clj
(defproject cljblog "0.1.0-SNAPSHOT"
:description "はじめてのWebアプリケーション"
:target-path "target/%s"
:profiles {:uberjar {:main cljblog.core
:aot :all}})
今回追加した依存ライブラリは、RingというClojureでWebアプリケーションを作る上で必要になるものです。詳細は次のPart2 Ringについて知るで説明するので、今は「こういうものだ」と思って先に進んでください。 次にREPLを起動して次のようなコードをREPL上で評価してみましょう。REPLはターミナルから起動しても、Emacsからproject.cljなどを開いた状態でM-x cider-jack-inとして起動しても構いません。 code:REPL(clojure)
;; => nil
user> (def server (jetty/run-jetty (fn _ {:body "Hello, world!"}) {:port 3000 :join? false})) ここまで評価したらhttp://localhost:3000をブラウザで表示してみましょう。すると「Hello, world!」が表示されているのが確認できるはずです。これがWebアプリケーションはじめの一歩です。
それぞれの式の意味を簡単に説明していきます。次のコードはring.adapter.jettyというネームスペースをrequireして、それに対してjettyとエイリアスをつけています。これによって、userネームスペースの中ではring.adapter.jettyと長たらしい名前を使わずに、このネームスペースの関数を呼び出すことができるようになります。
code:REPL(clojure)
次は実際にサーバーを起動する式です。jetty/run-jettyという関数はふたつ引数を受け取ります。第1引数はHTTPリクエストを受け取って、HTTPレスポンスを返す関数。第2引数はサーバーを起動するさいのオプションです。第1引数に渡した匿名関数はリクエストの内容を用いずに、レスポンスボディに"Hello, world"とだけ入れて返す関数です。第2引数のオプションはポート番号3000でリクエストを待ち受けるという意味です。:join?については開発時にはfalseを設定するとだけ覚えていればとりあえず良いです。
code:REPL(clojure)
user> (def server (jetty/run-jetty (fn _ {:body "Hello, world"}) {:port 3000 :join? false})) ブラウザで「Hello, world!」を確認したあとは、しっかりとサーバーを止めておきます。次のコードをREPLで評価します。
code:REPL(clojure)
user> (.stop server)
;; => nil
新しいネームスペースを作って保存する
今REPL上でやったことに少しだけ手を入れて、新しいネームスペースcljblog.serverを作成することにします。ファイルでいうとsrc/cljblog/server.cljというファイルになります。
code:src/cljblog/server.clj (clojure)
(ns cljblog.server
(defonce server (atom nil))
{:status 200
:headers {"Content-Type" "text/plain"}
:body "Hello, world!"})
(defn start []
(when-not @server
(reset! server (jetty/run-jetty handler options)))))
(defn stop []
(when @server
(.stop @server)
(reset! server nil)))
(defn restart []
(stop)
(start))
こうなりました。先程REPLで評価したものと比較すると、少しばかり様変わりしていますが基本的な部分は同じです。少し説明をすると最初のserver変数はjetty/run-jetty関数の返り値を受け取るために必要です。このファイルを何度読み込まれてもserver変数を再束縛しないようdefonceを利用しています。また、サーバーが起動か停止をする度に値を変更したいためatomを使います。
handler関数は、先程jetty/run-jetty関数に渡していた第1引数の匿名関数に名前を付けたものです。:body以外のキーも返すマップの中に追加していますが、これらは次のPartで詳しく説明します。
start, stop関数は既にサーバーが起動済みかどうかのチェックだけが加えています。start関数でserver変数がnilであるかをチェックして、nilであればサーバーを起動してserver変数を更新します。stop関数ではserver変数がnilでないことをチェックしていて、nilでなければserver変数のstopメソッドを呼び出しサーバーを停止させ、server変数をnilで更新します。こうすることでサーバーが二重に起動することを防いでいます。restart関数はstart, stop関数を順番に呼び出すだけで、簡単にサーバーを再起動させるための関数です。
そうしたらこのファイルを次のようにREPLからロードしてみます(EmacsでCIDERを利用している場合はM-x cider-load-fileを利用するとファイルをロードすることができます)。 code: (clojure)
user> (load-file "src/cljblog/server.clj")
次にREPLからこのネームスペースの関数を利用して、サーバーを起動して停止できることを確認します。
code: (clojure)
;; => nil
user> (server/start)
user> (server/stop)
;; => nil
REPLからサーバーを起動して停止できることが確認できました。また、Clojureを使ったアプリケーション開発では、このようにREPLを上手く使いながら開発をインタラクティブに行なっていくことで頭の片隅に置いておいてください。
このPartの仕上げ
最後にちょっとした仕上げをしましょう。cljblog.coreネームスペースを以下のように書き換えます。
code: src/cljblog/core.clj
(ns cljblog.core
(:gen-class))
(defn -main
"cljblogアプリを起動する"
args
(server/start))
こうすることによって、ターミナルからブログアプリを起動することができるようになりました。早速ターミナルから試してみましょう。
code: (shell)
$ lein run -m cljblog.core
2019-11-30 21:17:11.301:INFO::main: Logging initialized @3190ms to org.eclipse.jetty.util.log.StdErrLog
2019-11-30 21:17:11.430:INFO:oejs.Server:main: jetty-9.4.22.v20191022; built: 2019-10-22T13:37:13.455Z; git: b1e6b55512e008f7fbdf1cbea4ff8a6446d1073b; jvm 11.0.3+7
2019-11-30 21:17:11.467:INFO:oejs.AbstractConnector:main: Started ServerConnector@130b8c05{HTTP/1.1,http/1.1}{0.0.0.0:3000} 2019-11-30 21:17:11.467:INFO:oejs.Server:main: Started @3356ms
しつこいようですが、これでブラウザを確認して「Hello, world!」が表示されていれば成功です。終了するときはCtrl+cなどで終了させます。開発中はサーバーをREPLから立ち上げることが多いため、このようにターミナルから起動することはあまりありませんが簡単に動作確認をしたい場合などはこれで起動できると覚えておくと良いでしょう。