WhatisLuminus?
Luminus是基于一套轻量级类的微架构。它的目的是提供一个强大的,可扩展的,易于使用的平台。用Luminus你可以将重点放在开发你的应用程序上而不用在意其他的任何干扰。
Whydevelop web applications with Clojure?
Rapiddevelopment --快速发展
可以用REPL立即开始hacking和嵌入式开发服务器
Productivity--生产力
JVM结合Clojure意味着不用在生产力和性能之间做出选择
Interactivity --互动性
可以立即检测到你所作出的更改,而无需重新编译或重新启动
Flexibility--灵活性
选择适合你的组件,在项目的结构上有完全的控制权
Matureecosystem--成熟的生态系统
获得了许多现有的Clojure和Java库的支持
Powerfultools--功能强大的工具
用Leiningen来构建和部署应用程序很容易,可包括Heroku在内的享受一系列的部署选项
Yourfirstapplication
本教程将引导您使用Luminus动态通过建立一个简单的留言板程序。该留言薄允许用户留言、查看别人留下的消息。应用程序将显示HTML模板,基本的数据库访问,以及项目的结构。
如果你没有一个已经喜欢了的Clojure编辑器,那么建议你使用LightTable按照本教程操作。
要使用Luminus语言,你需要进行Leiningen安装。安装Leiningen是一个简单的过程,包括以下步骤。
1.下载脚本
2.把它设置成可执行的(如:chmod +x lein)
3.把它放在你的$路径。(如:~ /bin)
4.运行lein self-install并等待安装完成。
wget https://raw.github.com/technomancy/leiningen/stable/bin/lein chmod +x lein mv lein ~/bin lein self-install
一旦你Leiningen安装好后,你可以在终端运行以下命令来初始化你的应用:
leinnew luminus guestbook +h2
cdguestbook
以上将创建以H2嵌入式数据库为引擎支持一个新的模板工程。
现在我们可以按照如下步骤运行项目:
>leinring server
guestbookstarted successfully...
2013-03-01 19:05:30.389:INFO:oejs.Server:jetty-7.6.1.v20120215
Startedserver on port 3000
2013-03-01 19:05:30.459:INFO:oejs.AbstractConnector:StartedSelectChannelConnect
19:05:30.459:INFO:oejs.AbstractConnector:[email protected]:3000
将会弹出一个新的浏览器窗口,你应该可以看到你的应用程序在运行。请注意,如果你不想弹出一个新的浏览器,你可以运行:
leinring server-headless
你也可以通过如下自定义端口:
leinring server-headless 8000
新创建的应用程序具有以下结构
Procfile README.md project.clj src └ log4j.xml guestbook └ handler.clj layout.clj middleware.clj util.clj repl.clj db └ core.clj schema.clj routes └ home.clj test └ guestbook └ test └ handler.clj resources └ templates └ about.html base.html home.html └ public └ css └ screen.css fonts └ glyphicons-halflings-regular.eot glyphicons-halflings-regular.svg glyphicons-halflings-regular.ttf glyphicons-halflings-regular.woff img js md └ docs.md
让我们来看看在应用程序的根文件夹的文件的作用:
procfile -用于促进Heroku部署。
readme.md -应用程序文档通常存放处。
project.clj -用于由Leiningen管理项目的配置和依赖。
所有的代码均在SRC文件夹下。由于我们的应用程序称为留言簿,这是项目的根命名空间。让我们来看看已经为我们创造的所有的命名空间。
guestbook
handler.clj -定义应用程序的基本路线,这是程序的入口点
layout.clj -一个布局帮助的命名空间,我们定义的任何网页都需要将有他们的路径添加到这里
middleware.clj -一个命名空间包含程序自定义中间件
util.clj -用于一般的辅助功能,它是预先提供的MD - HTML助手
repl.clj -提供从REPL中来启动和停止应用的功能
log4j.xml记录配置
guestbook.db
db命名空间是用来定义程序的模型和处理数据持久层。
core.clj -用于存储函数用与与数据库交互
schema.clj-用于定义连接参数和数据库表
guestbook.routes
routes命名空间是在我们主页面的routes和controllers所在的位置。当你添加更多的routes,如authentication(认证),或specificworkflows(特定的工作流程),你应该在这里为他们创建的命名空间。
home.clj --一个定义home和程序相关页面的命名空间
这里用于安置我们程序的测试,已经为我们定义两个测试的样品。
这是用于安置我们程序所有的静态资源。已经有文件夹CSS, JavaScript,images,和 markdown被定义。
HTML templates
模板目录是用于表示应用程序页面的Selmer模板。
about.html -关于页
base.html-为网站的基本布局
html -首页
正如上面提到的,所有依赖关系管理通过更新project.clj文件。在根文件夹可找到我们已经创建了的应用程序的项目文件,它应该如下所示:
(defproject guestbook "0.1.0-SNAPSHOT" :url "http://example.com/FIXME" :description "FIXME: write description" :dependencies [[com.h2database/h2 "1.4.178"] [ring-server "0.3.1"] [environ "0.5.0"] [com.taoensso/timbre "3.2.1"] [markdown-clj "0.9.44"] [korma "0.3.1"] [com.taoensso/tower "2.0.2"] [selmer "0.6.6"] [org.clojure/clojure "1.6.0"] [log4j "1.2.17" :exclusions [javax.mail/mail javax.jms/jms com.sun.jdmk/jmxtools com.sun.jmx/jmxri]] [compojure "1.1.8"] [lib-noir "0.8.3"]] :plugins [[lein-ring "0.8.7"] [lein-environ "0.5.0"]] :ring {:handler guestbook.handler/app, :init guestbook.handler/init, :destroy guestbook.handler/destroy} :profiles {:uberjar {:aot :all} :production {:ring {:open-browser? false, :stacktraces? false, :auto-reload? false}}, :dev {:dependencies [[ring-mock "0.1.5"] [ring/ring-devel "1.2.2"]], :env {:dev true}}} :min-lein-version "2.0.0")
正如你所看到的project.clj仅仅是一个包含key/value描述应用不同的方面对Clojure列表。如果您需要添加任何自定义依赖zhixu简单地把它们添加到:dependencies vector(依赖向量)。
首先,我们将为应用程序创建一个模型,这样做,我们将开放位于src/guestbook/db 文件夹下得schema.clj文件。在这里,我们已经为我们的数据库连接做出了定义。定义一个简单的map,包含JDBC驱动程序JDBCdriver,协议protocol,用户user,密码password,和使用H2数据库的数据库文件。
(def db-spec {:classname "org.h2.Driver" :subprotocol "h2" :subname (str (io/resource-path) db-store) :user "sa" :password "" :naming {:keys clojure.string/upper-case :fields clojure.string/upper-case}})
接下来,我们有一个函数名为create-users-table内有user表定义(注)。我们会用一个create-guestbook-table 函数代替这个函数:
(defn create-guestbook-table [] (sql/db-do-commands db-spec (sql/create-table-ddl :guestbook [:id "INTEGER PRIMARY KEY AUTO_INCREMENT"] [:timestamp :timestamp] [:name "varchar(30)"] [:message "varchar(200)"])) (sql/db-do-prepared db-spec "CREATE INDEX timestamp_index ON guestbook (timestamp)"))
留言表将存储所有的域描述信息,例如注释者的姓名,邮件的内容和时间戳。
我们也将更新create-tables函数来调用它:
(defn create-tables "creates the database tables used by the application" [] (create-guestbook-table))
用创建的表,我们可以在我们的留言里加入读和写的功能。让我们打开src/guestbook/db/ core.clj文件并添加他们。我们看到,已经有一些处理用户表的代码。我们会用下面的代码替代他们:
(ns guestbook.db.core (:use korma.core [korma.db :only (defdb)]) (:require [guestbook.db.schema :as schema])) (defdb db schema/db-spec) (defentity guestbook) (defn save-message [name message] (insert guestbook (values {:name name :message message :timestamp (new java.util.Date)}))) (defn get-messages [] (select guestbook))
上面我们创建一个实体来代表我们在架构命名空间里创建留言表。然后我们添加函数save-message 和get-messages与它互动。
句柄handler命名空间包含一个函数init。当应用程序启动时,这个函数将被调用一次。让我们添加代码以检查数据库是否已经初始化,如果需要则进行初始化。
我们首先需要参考架构命名空间为了使用initialized? 以及 create-tables的功能。
(ns guestbook.handler (:use ...) (:require ... [guestbook.db.schema :as schema]))
接下来,我们更新init函数
(defn init "init will be called once when app is deployed as a servlet on an app server such as Tomcat put any initialization code here" [] (timbre/set-config! [:appenders :rotor] {:min-level :info :enabled? true :async? false ; should be always false for rotor :max-message-per-msecs nil :fn rotor/appender-fn}) (timbre/set-config! [:shared-appender-config :rotor] {:path "guestbook.log" :max-size (* 512 1024) :backlog 10}) (if (env :selmer-dev) (parser/cache-off!)) ;;initialize the database if needed (if-not (schema/initialized?) (schema/create-tables)) (timbre/info "guestbook started successfully"))
由于我们改变了我们程序的init函数,所以关掉服务(ctrl+c)并重新启动服务(lein ring server)
我们的routes均定义在guestbook.routes.home 命名空间里. 打开它,从数据库中读取信息的跳转需要增加一些逻辑.首先,需要增加referenc给我们的 db 命名空间:
(ns guestbook.routes.home (:use ...) (:require ... [guestbook.db.core :as db]))
接下来,改变 home-page 控制器:
(defn home-page [& [name message error]] (layout/render "home.html" {:error error :name name :message message :messages (db/get-messages)}))
目前,我们可以为模板增加一些额外的参数,参数是从数据库中的信息列表.
我们希望用户可以发布新messages,因此需要在handler增加一个controller theform posts:
(defn save-message [name message] (cond (empty? name) (home-page name message "Somebody forgot to leave a name") (empty? message) (home-page name message "Don't you have something to say?") :else (do (db/save-message name message) (home-page))))
最后,我们对controller添加routes 到 home-routes 定义中:
(defroutes home-routes (GET "/" [] (home-page)) (POST "/" [name message] (save-message name message)) (GET "/about" [] (about-page)))
现在,我们的控制器已经setup,打开 home.html 模板.
{% extends "templates/base.html" %} {% block content %} <div class="jumbotron"> <h1>Welcome to guestbook</h1> <p>Time to start building your site!</p> <p><a class="btn btn-primary btn-lg" href="http://luminusweb.net">Learn more »</a></p> </div> <div class="row-fluid"> <div class="span8"> {{content|safe}} </div> </div> {% endblock %}
更新内容,使其能够循环展现消息列表:
{% extends "templates/base.html" %} {% block content %} <div class="jumbotron"> <h1>Welcome to guestbook</h1> </div> <div class="row-fluid"> <div class="span8"> <ul> {% for item in messages %} <li> <time>{{item.timestamp|date:"yyyy-MM-dd HH:mm"}}</time> <p>{{item.message}}</p> <p> - {{item.name}}</p> </li> {% endfor %} </ul> </div> </div> {% endblock %}
就如你看到的,我们使用iterator去跑messages.每个message 都是一个 map ,包含message, name, 和 timestamp 关键字, 我们可以通过 name 获得它. 另外,注意 useof the date filter to format the timestamps into a human readableform.
接下来,为了展现控制器可能出现的错误需要增加 error 模块:
{% if error %} <p class="error">{{error}}</p> {% endif %}
简单测试一下error模块是否起作用。最后,创建一个允许用户提交message的form:
<form action="/" method="POST"> <p> Name: <input type="text" name="name" value={{name}}> </p> <p> Message: <textarea rows="4" cols="50" name="message"> {{message}} </textarea> </p> <input type="submit" value="comment"> </form>
home.html template 应该是这样的:
{% extends "templates/base.html" %} {% block content %} <div class="jumbotron"> <h1>Welcome to guestbook</h1> </div> <div class="row-fluid"> <div class="span8"> <ul> {% for item in messages %} <li> <time>{{item.timestamp|date:"yyyy-MM-dd HH:mm"}}</time> <p>{{item.message}}</p> <p> - {{item.name}}</p> </li> {% endfor %} </ul> {% if error %} <p class="error">{{error}}</p> {% endif %} </div> <div class="span8"> <form action="/" method="POST"> <p> Name: <input type="text" name="name" value="{{name}}" /> </p> <p> Message: <textarea rows="4" cols="50" name="message" />{{message}}</textarea> </p> <input type="submit" value="comment" /> </form> </div> </div> {% endblock %}
最后,更新 screen.css 文件,让页面更美观:
body { height: 100%; padding-top: 70px; font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; line-height: 1.4em; background: #eaeaea; color: #4d4d4d; width: 550px; margin: 0 auto; -webkit-font-smoothing: antialiased; -moz-font-smoothing: antialiased; -ms-font-smoothing: antialiased; -o-font-smoothing: antialiased; font-smoothing: antialiased; } input[type=submit] { margin: 0; padding: 0; border: 0; line-height: 1.4em; background: none; vertical-align: baseline; } input[type=submit], textarea { font-size: 24px; font-family: inherit; border: 0; padding: 6px; border: 1px solid #999; box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); -moz-box-sizing: border-box; -ms-box-sizing: border-box; -o-box-sizing: border-box; box-sizing: border-box; } input[type=submit]:hover { background: rgba(0, 0, 0, 0.15); box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3); } textarea { position: relative; line-height: 1em; width: 100%; } .error { font-weight: bold; color: red; } .jumbotron { position: relative; background: white; z-index: 2; border-top: 1px dotted #adadad; } h1 { width: 100%; font-size: 70px; font-weight: bold; text-align: center; } ul { margin: 0; padding: 0; list-style: none; } li { position: relative; font-size: 16px; padding: 5px; border-bottom: 1px dotted #ccc; box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.15); } li:last-child { border-bottom: none; } li time { font-size: 12px; padding-bottom: 20px; } form:before, .error:before { content: ''; position: absolute; top: 0; right: 0; left: 0; height: 15px; border-bottom: 1px solid #6c615c; background: #8d7d77; } form, .error { width: 520px; padding: 30px; margin-bottom: 50px; background: #fff; border: 1px solid #ccc; position: relative; box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.15); } form input { width: 50%; clear: both; }
重新加载页面,会有新发现. 试着增加 comment 看看是否起作用.
打包:
lein ring uberjar
直接运行包:
java -jartarget/guestbook-0.1.0-SNAPSHOT-standalone.jar
如果想部署的话,比如Apache Tomcat,运行:
lein ring uberwar
可以部署在你想要的地方。