Luminus A Clojure web framework

WhatisLuminus?

Luminus是基于一套轻量级类的微架构。它的目的是提供一个强大的,可扩展的,易于使用的平台。用Luminus你可以将重点放在开发你的应用程序上而不用在意其他的任何干扰。

Whydevelop web applications with Clojure?

Rapiddevelopment --快速发展

可以用REPL立即开始hacking和嵌入式开发服务器

Productivity--生产力

JVM结合Clojure意味着不用在生产力和性能之间做出选择

Interactivity --互动性

可以立即检测到你所作出的更改,而无需重新编译或重新启动

Flexibility--灵活性

选择适合你的组件,在项目的结构上有完全的控制权

Matureecosystem--成熟的生态系统

获得了许多现有的Clojure和Java库的支持

Powerfultools--功能强大的工具

用Leiningen来构建和部署应用程序很容易,可包括Heroku在内的享受一系列的部署选项

Yourfirstapplication

1.Guestbook Application

本教程将引导您使用Luminus动态通过建立一个简单的留言板程序。该留言薄允许用户留言、查看别人留下的消息。应用程序将显示HTML模板,基本的数据库访问,以及项目的结构。

如果你没有一个已经喜欢了的Clojure编辑器,那么建议你使用LightTable按照本教程操作。

2.Installing Leiningen

要使用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

3.Creating a new application

一旦你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

4.Anatomy of a Luminus application

新创建的应用程序具有以下结构

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管理项目的配置和依赖。

5.The Source Directory

所有的代码均在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和程序相关页面的命名空间

6.The Test Directory

这里用于安置我们程序的测试,已经为我们定义两个测试的样品。

7.The ResourcesDirectory

这是用于安置我们程序所有的静态资源。已经有文件夹CSS, JavaScript,images,和 markdown被定义。

HTML templates

模板目录是用于表示应用程序页面的Selmer模板。

about.html -关于页

base.html-为网站的基本布局

html -首页

8.Adding Dependencies

正如上面提到的,所有依赖关系管理通过更新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(依赖向量)。

9.Accessingthe Database

首先,我们将为应用程序创建一个模型,这样做,我们将开放位于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与它互动。

10.Running Code on Startup

   句柄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)

11.Creating Pages and HandlingForm Input

我们的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 &raquo;</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 看看是否起作用.

12.Packaging the application

打包:

lein ring uberjar

直接运行包:

java -jartarget/guestbook-0.1.0-SNAPSHOT-standalone.jar

如果想部署的话,比如Apache Tomcat,运行:

lein ring uberwar

可以部署在你想要的地方。



 

 

 

 

 

 

 

 

 


你可能感兴趣的:(Luminus A Clojure web framework)