在做一个项目中,我们会时不时的把我们的代码重构,重构的目地就是让代码更清晰,K.I.S.S 原则是我们一直追求的,同时 Clojure 最出名的就是 sample made easy,所以我们要时不时的重构,我们也应用了 clojure 常见的 reloadable 风格,还有出名的 The Twelve-Factor App。
我们要根据不同的环境来分配不同的配置,首先我们要加载不同的配置。这里我们要用到一个库来读取配置,cprop 库是我们使用的依赖库 mount 作者开发的另一个库,用来方便的加载配置文件,首先先加入库:
[cprop "0.1.13"]
然后在 src/clj 新建 soul-talk.config 命名空间,并加入读取配置的函数,同样的配置的读取也用 mount 启动:
(ns soul-talk.config
(:require [mount.core :refer [args defstate]]
[cprop.core :refer [load-config]]
[cprop.source :as source]))
(defstate env :start (load-config
:merge
[(args)]))
然后我们在项目目录下 env/dev/clj 下新建 soul-talk.env 命名空间,先加入一个初始化的配置:
(ns soul-talk.env
(:require [taoensso.timbre :as log]
[selmer.parser :as parser]))
(def defaults
{:init
(fn []
(parser/cache-off!)
(log/info "====[soul_talk started successfully using the development profile]===="))
:stop
(fn []
(log/info "====[soul_talk has shut down]====="))})
然后在 env/dev 下新建 resources 目录,这里用来放开发的资源文件,在这个目录下新建 config.cdn 文件:
{:dev true
:port 300}
同时我们在这个目录下也建立 logback.xml 的配置,记录我们的日志:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<charset>UTF-8</charset>
<pattern>%date{ISO8601} [%thread] %-5level %logger{36} - %msg %n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>log/soul_talk.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/soul_talk.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- keep 30 days of history -->
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<charset>UTF-8</charset>
<pattern>%date{ISO8601} [%thread] %-5level %logger{36} - %msg %n</pattern>
</encoder>
</appender>
<logger name="org.xnio.nio" level="warn">
<AppenderRef ref="STDOUT"/>
<AppenderRef ref="FILE"/>
</logger>
<logger name="com.zaxxer.hikari" level="warn">
<AppenderRef ref="STDOUT"/>
<AppenderRef ref="FILE"/>
</logger>
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>
然后我们要修改 src/clj 下 soul-talk.handler 中的函数,并且加入初始化的配置读取:
(ns soul-talk.handler
(:require [soul-talk.middleware :as middleware]
[soul-talk.layout :as layout]
[soul-talk.env :refer [defaults]]
[compojure.route :as route]
[compojure.core :refer [routes wrap-routes]]
[soul-talk.routes.home :refer [home-routes auth-routes]]
[soul-talk.routes.services :refer [services-routes]]
[mount.core :as mount]))
(mount/defstate init-app
:start ((or (:init defaults) identity))
:stop ((or (:stop defaults) identity)))
(def app
(-> (routes
services-routes
(-> #'home-routes
(wrap-routes middleware/wrap-csrf))
(-> #'auth-routes
(wrap-routes middleware/wrap-csrf)
(wrap-routes middleware/wrap-session-auth))
(route/not-found
(:body
(layout/error-page
{:status 404
:title "页面未找到"}))))
(middleware/wrap-base)))
然后修改 soul-talk.core 命名空间:
(ns soul-talk.core
(:require [ring.adapter.jetty :as jetty]
[soul-talk.config :refer [env]]
[soul-talk.handler :refer [app]]
[mount.core :refer [defstate]]
[mount.core :as mount]
[taoensso.timbre :as log])
(:gen-class))
(defn start-system []
(-> #'app
(jetty/run-jetty
{:port 3000
:join? false})))
(defstate ^{:on-reload :noop}
system
:start (start-system)
:stop (.stop system))
(defn stop-app []
(doseq [component (:stopped (mount/stop))]
(log/info component "stopped"))
(shutdown-agents))
(defn start-app [args]
(doseq [component (-> args
mount/start-with-args
:started)]
(log/info component "started"))
(.addShutdownHook (Runtime/getRuntime) (Thread. stop-app)))
(defn -main [& args]
(start-app args))
然后加到 project.clj 配置相关的:
:profiles
{:dev {:source-paths ["env/dev/clj"]
:resource-paths ["env/dev/resources"]}}
我这里可能是 :dev 因为我用了lein-cljsbuild 的 map 配置,你那里可能是 vector 配置,你要查找 :id “dev”中的配置。
为了能打印出 mount 的日志,我们要引入 mount-up 包,
[tolitius/mount-up "0.1.1"]
这样我们在控制台就可以看到 mount 都加载了什么。
在env/dev/clj 下在 dev 命名空间中加入:
(ns dev
(:require [soul-talk.models.db :refer [*db*]]
[clojure.pprint :refer [pprint]]
[clojure.tools.namespace.repl :as tn]
[mount.tools.graph :refer [states-with-deps]]
[figwheel :refer [start-fw stop-fw cljs]]
[soul-talk.handler]
[soul-talk.core]
[soul-talk.config]
[ragtime.jdbc :as jdbc]
[ragtime.repl :refer [migrate rollback]]
[mount.core :as mount]
[cprop.core :refer [load-config]]
[mount-up.core :as mu]))
(mu/on-upndown :info mu/log :before)
(defn start []
(mount/start
#'soul-talk.handler/init-app
#'soul-talk.config/env
#'soul-talk.models.db/*db*
#'soul-talk.core/system))
下面我们来做打包的工作,我们要在 project.clj 中添加配置:
:profiles
{:uberjar
{:omit-source true
:prep-tasks ["compile" ["cljsbuild" "once" "prod"]]
:cljsbuild
{:builds
{:prod
{:source-paths ["src/cljs" "src/cljc" "env/prod/cljs"]
:compiler {:output-to "resources/public/js/main.js"
:optimizations :advanced
:pretty-print false}}}}
:aot :all
:uberjar-name "soul-talk.jar"
:source-paths ["env/prod/clj"]
:resource-paths ["env/prod/resources"]}}
:prep-tasks 这个选项表示我们先编译 cljs,然后再进行打 jar 包。
我们要在 env/prod/clj 下新建 evn.clj
(ns soul-talk.env
(:require [taoensso.timbre :as log]))
(def defaults
{:init
(fn []
(log/info "====[soul-talk started successfully]===="))
:stop
(fn []
(log/info "====[soul-talk has shut down successfully]====="))})
在 env/prod/cljs 下新建 soul-talk.prod 的 cljs 命名空间:
(ns soul-talk.prod
(:require [soul-talk.core :as core]))
;;关闭打印输出
(set! *print-fn* (fn [& _]))
(core/init!)
在 env/prod/resources 下新建 config.edn:
{:production true
:port 3000}
同时新建 logback.xml:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>log/soul_talk.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/soul_talk.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- keep 30 days of history -->
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<charset>UTF-8</charset>
<pattern>%date{ISO8601} [%thread] %-5level %logger{36} - %msg %n</pattern>
</encoder>
</appender>
<logger name="org.xnio.nio" level="warn">
<AppenderRef ref="FILE"/>
</logger>
<logger name="com.zaxxer.hikari" level="warn">
<AppenderRef ref="FILE"/>
</logger>
<root level="INFO">
<appender-ref ref="FILE" />
</root>
</configuration>
这样我们在命令行运行, lein uberjar:
➜ soul-talk git:(master) ✗ lein uberjar
Compiling soul-talk.env
Compiling soul-talk.layout
Compiling soul-talk.middleware
Compiling soul-talk.core
Compiling soul-talk.models.tag-db
Compiling soul-talk.models.db
Compiling soul-talk.models.comment-db
Compiling soul-talk.models.post-tag-db
Compiling soul-talk.models.category-db
Compiling soul-talk.models.user-db
Compiling soul-talk.models.post-db
Compiling soul-talk.pagination
Compiling soul-talk.handler
.....
target/cljsbuild-compiler-0/cljs_time/core.cljs
WARNING: Use of undeclared Var cljs-time/core at line 634 target/cljsbuild-compiler-0/cljs_time/core.cljs
WARNING: No such namespace: cljs-time, could not locate cljs_time.cljs, cljs_time.cljc, or JavaScript source providing "cljs-time" (Please check that namespaces with dashes use underscores in the ClojureScript file name) at line 301 target/cljsbuild-compiler-0/cljs_time/format.cljs
WARNING: Use of undeclared Var cljs-time/core at line 301 target/cljsbuild-compiler-0/cljs_time/format.cljs
WARNING: No such namespace: cljs-time, could not locate cljs_time.cljs, cljs_time.cljc, or JavaScript source providing "cljs-time" (Please check that namespaces with dashes use underscores in the ClojureScript file name) at line 305 target/cljsbuild-compiler-0/cljs_time/format.cljs
WARNING: Use of undeclared Var cljs-time/core at line 305 target/cljsbuild-compiler-0/cljs_time/format.cljs
WARNING: No such namespace: cljs, could not locate cljs.cljs, cljs.cljc, or JavaScript source providing "cljs" at line 311 target/cljsbuild-compiler-0/cljs_time/format.cljs
WARNING: Use of undeclared Var cljs/core at line 311 target/cljsbuild-compiler-0/cljs_time/format.cljs
WARNING: domina is a single segment namespace at line 1 target/cljsbuild-compiler-0/domina.cljs
Successfully compiled ["resources/public/js/main.js"] in 82.849 seconds.
Created /Users/jiesoul/projects/private/soul-talk/target/soul-talk-0.1.0.jar
Created /Users/jiesoul/projects/private/soul-talk/target/soul-talk.jar
看到最后输出 jar 了,这样我们就可以直接在命令行运行了:
➜ soul-talk git:(master) ✗ java -jar target/soul-talk.jar
18-10-09 11:49:01 MacBook-Pro.local INFO [soul-talk.core:30] - #'soul-talk.config/env started
18-10-09 11:49:01 MacBook-Pro.local INFO [soul-talk.core:30] - #'soul-talk.models.db/*db* started
18-10-09 11:49:01 MacBook-Pro.local INFO [soul-talk.core:30] - #'soul-talk.core/system started
这样我们的服务就运行起来了,你可以打开http://localhost:3000/ 查看。