なんとな~くしあわせ?の日記

「そしてそれゆえ、知識そのものが力である」 (Nam et ipsa scientia potestas est.) 〜 フランシス・ベーコン

ClojureとREPL駆動開発

REPL駆動開発

またClojureです。REPL使ってやりたいことが即座にできなかったので、やり方をまとめました。

REPLを自分のプロジェクトの名前空間で起動

clojure - How to Run Code Using Leiningen? - Stack Overflow

user> (in-ns 'myproject.core)

名前空間内のシンボルを全て表示

clojure - How to list the functions of a namespace? - Stack Overflow

user> (require 'myproject.core)
user> (keys (ns-publics 'myproject.core))

Clojureのファイル編集後にREPLに反映

How to reload a clojure file in REPL - Stack Overflow

;; まず読み込んでシンボル一覧表示
user> (require 'myproject.core)
user> (keys (ns-publics 'myproject.core))

;; 〜ファイル編集〜

;; (refresh)を使う
user> (use '[clojure.tools.namespace.repl :only (refresh)])
user> (refresh)

;; シンボル一覧表示 → 更新されてる
user> (require 'myproject.core)
user> (keys (ns-publics 'myproject.core))

REPLからshellを呼び出し(たぶんLinux/Mac/msys上でしかうまく動かない)

sh - clojure.java.shell | ClojureDocs - Community-Powered Clojure Documentation and Examples

user> (use '[clojure.java.shell :only [sh]])
user> (println (:out (sh "cowsay" "Printing a command-line output")))
 ________________________________
< Printing a command-line output >
 --------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

nil

Monger + mLab + ClojureでMongoDBに触る

MongoDBはいわゆるNoSQLで、きっちり行と列を定めなければいけないRDBと違い、JSONをそのままぶっこめる。
今趣味で触っているプログラムに使おうと思い、Mongerをクライアントライブラリとして使用してみた。

準備編

Monger

ClojureのMongoDBクライアント側ライブラリ。使いはじめは以下のページから:
Monger, a Clojure MongoDB client: Connecting to MongoDB | MongoDB library for Clojure

project.cljに以下を追加するだけ

[com.novemberain/monger "3.1.0"]

mLab

MongoDBをクラウドで提供しているPaaS、今見たらTOYOTAも使ってるようだ。無料枠がある。
www.mlab.com

HerokuのAdd-onからmLabを使う

Herokuの側でAdd-onを追加したら、Herokuの環境変数MONGODB_URIに全ての認証情報が入っている。
うーん、これを使用してやればもっと簡単にできたな。

mLabのダッシュボードに入ると、以下のような情報がもらえる

Database: heroku_xxxxxxxx
To connect using the mongo shell:
mongo ds12345.mlab.com:12345/heroku_xxxxxxxx -u -p
To connect using a driver via the standard MongoDB URI (what's this?):
mongodb://:@ds12345.mlab.com:12345/heroku_xxxxxxxx

mLabでMongoDBのユーザーを作成する

自動作成する接続情報とは別にユーザーを作ることも出来る。

mLabの管理ポータルにログインする

  1. アカウントのページからデプロイのページへ(動きが正しければ、ユーザーを追加するためのデータベースに遷移するはず)
  2. 「ユーザー」タブをクリック
  3. 「データベースユーザーを追加」をクリック、新しいユーザーを追加する
  4. 追加したら以下のコマンドでリモートのMongoDBにログインできることを確認する
mongo ds12345.mlab.com:12345/heroku_xxxxxxxx -u <dbuser> -p<dbpassword>

コード片

これでやっと接続できる、わたしはとりあえず環境変数にいろいろ情報を与えて使ってみた。

  • mg/connect だと、ホスト名とポートをハッシュマップで渡すようだ
  • mg/connect-with-credentials だと、サーバ名は文字列にしないとだめなようだ
  • mg/connect-via-uri だと、環境変数MONGODB_URIが使えるのだと思う
  • let [db (or (System/getenv "DB_NAME") "")] とやると、環境変数から文字列が取れなかった時に空文字を入れられる
  • Rubyにおけるnilガードみたいなやつですね
  • とりあえずこれでJavaでいうところのConnectionがとれる
(ns sample
  (:gen-class
   :main false)
  (:require [clojure.string :as str]
            [monger.core :as mg]
            [monger.credentials :as mcred]))

(defn mongodb []
  (let [db   (or (System/getenv "DB_NAME") "")
        user (or (System/getenv "DB_USER") "")
        pass (or (System/getenv "DB_PASS") "")
        host (or (System/getenv "DB_HOST") "127.0.0.1")
        port (Integer/parseInt (or (System/getenv "DB_PORT") "27017"))]

    (if (or (str/blank? user) (str/blank? pass))
      ;; ユーザーやパスワードが設定されてないので開発環境
      (mg/connect {:host host :port port})
      ;; 本番環境
      (mg/connect-with-credentials (str host ":" port) (mcred/create user db pass))
    )))

結局接続するだけで今日は終わってしまった。レコードを作りたかったのだが。

leiningenで作るuberjarがmavenでできたらいいと思ったら出来なかった話

Leiningenでuberjarを作る

Clojureの日本語ガイドにあるように -> Part7: どのようにして Heroku へデプロイするか Leiningenから lein uberjar と打てばいわゆるFAT Jarができる。これは依存ライブラリを全て含んでいるのでJavaさえあれば実行できる。

Mavenからそれはできないか

似たようなものはできた。以下、設定したproject.clj

  • 肝心な部分は maven-shade-plugin
    • こいつが依存ライブラリとコンパイル済みのClojureからできたクラスファイルをjarに入れてくれる
  • ManifestResourceTransformerMaven側でManifest.mfを書き換えて、実行するメインクラスを定めてくれる
  • finalName
    • これを ${project.artifactId}-${project.version}-standalone で設定すると、leiningenがデフォルトで出力するuberjarの名前になるハズ

実行は lein pom してから mvn clojure:compile package

(defproject uber "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :uberjar-name "uber-clj.jar"
  :min-lein-version "2.5.3"
  :dependencies [[org.clojure/clojure "1.8.0"]]
  :pom-plugins [[com.theoryinpractise/clojure-maven-plugin "1.3.8"
                 {:configuration ([:mainClass "uber.core"]
                                  [:sourceDirectories [:sourceDirectory "src/main/clj"]])}]
                [org.apache.maven.plugins/maven-shade-plugin "2.4.3"
                 [:executions [:execution ([:phase "package"]
                                           [:goals [:goal "shade"]]
                                           [:configuration
                                            [:transformers
                                             [:transformer
                                              {:implementation "org.apache.maven.plugins.shade.resource.ApacheLicenseResourceTransformer"}]
                                             [:transformer
                                              {:implementation "org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformer"}]
                                             [:transformer
                                              {:implementation "org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"}
                                              [:mainClass "uber.core"]]
                                             ]
                                            ;[:finalName "${project.artifactId}-${project.version}-standalone"]
                                            [:finalName "uber-clj"]
                                            [:filters
                                             [:filter
                                              [:includes
                                               [:include "**/*.js"]
                                               [:include "**/*.class"]
                                               [:include "**/*.xml"]]]]])]]]]
  :source-paths ["src/main/clj"]
  :test-paths ["src/test/clj"]
  :resource-paths ["resources"]
  :profiles
  {:dev {:env {:dev true }}
   :uberjar {:aot :all
             :main uber.core}})

動かない理由

ただし、この方法で作ったjarは動かない。それにはどうやらClojureのAOTコンパイルが関係しているらしい。

ClojureのAOTコンパイルは、タイムスタンプによってAOTコンパイル済みか判定する。maven-shade-pluginはこれをおかしくさせてしまうようだ。 むーん、maven-shade-pluginにプルリクする?

追記

簡単なスクリプト程度だと、Mavenから動かしても動くバイナリができるようだ。ringなどのライブラリを混ぜると動かない感じになる。