这是我的“ Clojure Web开发”系列的第2部分。 您可以在此 Reddit线程上讨论第一部分。 阅读这些评论后,我必须解释我编写本系列文章的两个假设:
- 让Clojure以外的人(尤其是Java开发人员)易于理解。 这就是为什么我使用REST / JSON来支持传递和组件作为“依赖注入”实现的原因,可以很容易地将其解释为等效于Spring框架。 Om有点冗长,也是如此,但我认为它比其他React包装器更容易理解,并且被广泛采用。
- 使事情易于在开发人员机器上引导。 这是一个动手实践,所有单独的步骤都已提交给GitHub。 这就是为什么我使用MongoDB的原因,它不是将应用程序扩展到数百万用户的最佳选择,但是它非常适合引导-无需模式,表,仅插入数据即可开始工作。 我强烈推荐Honza Kral 多语种持久性演讲,他鼓励在项目开始时就从简单开始并进行优化,以使开发人员感到满意。
在上一篇文章中,我们使用Clojurescript前端引导了一个基本的Web应用程序,该应用程序使用REST前端(目前为静态)来提供REST数据,由于重新加载了repl和figwheel,它们都可以完全重新加载。 您可以在此分支中找到它的最终工作版本。
今天,我们将显示存储在MongoDB中的联系人列表。 我假设您已安装MongoDB(如果未安装)–与docker无关紧要。
从数据库提供联系列表
好的,让我们开始吧。 在后端,我们需要向project.clj添加一些依赖项:
:dependencies
...
[org.danielsz/system "0.1.9"]
[com.novemberain/monger "2.0.0"]]
monger
是Mongo Java驱动程序的惯用Clojure包装器,而system
是包括Mongo(类似于Spring的Spring Data)在内的各种数据存储组件的不错集合。
为了与数据存储进行交互,我喜欢使用抽象存储库的概念。 这应该对调用者隐藏实现细节,并允许将来切换到另一个商店。 因此,让我们在components/repo.clj
创建一个抽象接口(在Clojure –协议中):
(ns modern-clj-web.component.repo)
(defprotocol ContactRepository
(find-all [this]))
我们需要此参数作为参数,以允许Clojure运行时调度此存储库的正确实现。 使用Monger实施Mongo非常简单:
(ns modern-clj-web.component.repo
(:require [monger.collection :as mc]
[monger.json]))
...
(defrecord ContactRepoComponent [mongo]
ContactRepository
(find-all [this]
(mc/find-maps (:db mongo) "contacts")))
(defn new-contact-repo-component []
(->ContactRepoComponent {}))
这里要注意的事情:
-
mc/find-maps
仅将集合中的所有记录作为Clojure映射返回 -
ContactComponent
注入了由系统库创建的mongo组件,该组件在:db
键下添加了Mongo客户端 - 由于此组件是无状态的,因此我们无需实现
component/Lifecycle
接口,但仍可以像典型的了解生命周期的组件那样将其连接到系统中 - 需要
monger.json
添加了对Mongo类型(例如ObjectId
)的JSON序列化支持。
好的,现在该在终端节点/example.clj中使用我们的新组件了:
(:require
...
[modern-clj-web.component.repo :as r])
(defn example-endpoint [{repo :contact-repo}]
(routes
...
(GET "/contacts" [] (response (r/find-all repo)))
{repo :contact-repo}
标记(解构)会自动将系统地图中的:contact-repo
键绑定到repo
值。 因此,我们需要将组件分配给system.clj
中的键:
(:require
...
[modern-clj-web.component.repo :refer [new-contact-repo-component]]
[system.components.mongo :refer [new-mongo-db]])
(-> (component/system-map
:app (handler-component (:app config))
:http (jetty-server (:http config))
:example (endpoint-component example-endpoint)
:mongo (new-mongo-db (:mongo-uri config))
:contact-repo (new-contact-repo-component))
(component/system-using
{:http [:app]
:app [:example]
:example [:contact-repo]
:contact-repo [:mongo]}))))
简而言之–我们使用系统的new-mongo-db
创建Mongo组件,使其成为存储库的依赖项,而存储库本身就是示例端点的依赖项。
最后,我们需要在config.clj
配置:mongo-uri
config属性:
(def environ
{:http {:port (some-> env :port Integer.)}}
:mongo-uri "mongodb://localhost:27017/contacts"})
要检查它是否工作正常,请重新启动repl,再次输入(go)
并进行GET到http:// localhost:3000 / contacts 。
curl http://localhost:3000/contacts
[]
好的,因为我们在Mongo数据库中没有任何数据,所以我们得到了一个空列表。 让我们用mongo控制台添加一些内容:
mongo localhost:27017/contacts
MongoDB shell version: 2.4.9
connecting to: localhost:27017/contacts
> db.contacts.insert({firstname: "Al", lastname: "Pacino"});
> db.contacts.insert({firstname: "Johnny", lastname: "Depp"});
最后,我们的端点应返回以下两个记录:
curl http://localhost:3000/contacts
[{"lastname":"Pacino","firstname":"Al","_id":"56158345fd2dabeddfb18799"},{"lastname":"Depp","firstname":"Johnny","_id":"56158355fd2dabeddfb1879a"}]
甜! 再次-如有任何问题,请检查此commit 。
从ClojureScript获取联系人
在这一步中,我们将在ClojureScript前端上通过AJAX调用获取联系人。 像往常一样,我们需要很少的project.clj
依赖项作为开始:
:dependencies
...
[org.clojure/clojurescript "1.7.48"]
[org.clojure/core.async "0.1.346.0-17112a-alpha"]
[cljs-http "0.1.37"]
使用figwheel应该已经可以看到figwheel
,但是最好显式地要求特定的版本。 cljs-http
是ClojureScript和core.async
的HTTP客户端core.async
为CSP模型中的异步通信提供了便利,在ClojureScript中特别有用。 让我们看看它在实践中是如何工作的。
要进行AJAX调用,我们需要从cljs-http.client
调用方法,因此我们将其添加到core.cljs
:
(ns ^:figwheel-always modern-clj-web.core
(:require [cljs-http.client :as http]))
(println (http/get "/contacts"))
您应该看到#object[cljs.core.async.impl.channels.ManyToManyChannel]
。 那是什么疯狂???
这是我们进入core.async
的时间。 处理来自Javascript的异步网络调用的最常见方法是使用回调或Promise。 core.async
方法是使用通道。 它使您的代码看起来更像是一系列同步调用,并且更容易推理。 因此, http/get
函数返回一个通道,当响应到达时,将在该通道上发布结果。 为了接收该消息,我们需要使用<!
功能。 由于这阻塞了,所以我们也需要像go语言一样用go
宏包围这个调用。 因此,获取联系的正确方法如下所示:
(:require
...
[cljs.core.async :refer [<! >! chan]])
(go
(let [response (<! (http/get "/contacts"))]
(println (:body response))))
添加Om组件
在不引入任何结构的情况下处理前端代码可能很快成为一场噩梦。 在2015年末,我们在该领域基本上拥有两个主要的JS框架:Angular nad React。 ClojureScript范例(功能编程,不可变数据结构)非常适合React哲学。 简而言之,React应用程序由将数据作为输入并渲染HTML作为输出的组件组成。 输出不是真实的DOM,而是所谓的虚拟DOM ,它有助于计算从当前视图到更新视图的差异。
在很多人的反应在ClojureScript我喜欢的包装使用庵与OM-工具 ,以减少一些冗长。 让我们将其介绍到我们的project.clj
:
:dependencies
...
[org.omcljs/om "0.9.0"]
[prismatic/om-tools "0.3.12"]
要渲染“ hello world”组件,我们需要在core.cljs
添加一些代码:
(:require
...
[om.core :as om]
[om-tools.core :refer-macros [defcomponent]]
[om-tools.dom :as dom :include-macros true]))
(def app-state (atom {:message "hello from om"}))
(defcomponent app [data owner]
(render [_]
(dom/div (:message data))))
(om/root app app-state
{:target (.getElementById js/document "main")})
这里发生了什么? Om的主要概念是将整个应用程序状态保持在一个全局原子中 ,这是Clojure的状态管理方式。 因此,我们将此app-state
映射(包装在atom
)作为参数传递给om/root
,该参数将组件安装到实际DOM中(来自index.html
<div id="main"/>
)。 该app
组件仅显示:message
值,因此您应该看到“来自om的问候”。 如果您正在运行fighweel
,则可以更改消息值,并且该消息值应立即更新。
最后,让我们与Om建立联系:
(defn get-contacts []
(go
(let [response (<! (http/get "/contacts"))]
(:body response))))
(defcomponent contact-comp [contact _]
(render [_]
(dom/li (str (:firstname contact) " " (:lastname contact)))))
(defcomponent app [data _]
(will-mount [_]
(go
(let [contacts (<! (get-contacts))]
(om/update! data :contacts contacts))))
(render [_]
(dom/div
(dom/h2 (:message data))
(dom/ul
(om/build-all contact-comp (:contacts data))))))
因此, contact-comp
仅呈现单个联系人。 我们使用om/build-all
使所有联系人在全局状态下的:contacts
字段中可见。 最棘手的部分–当app
组件即将被安装到DOM时,我们will-mount
使用will-mount
生命周期方法从服务器获取联系人。
同样,在出现任何问题的情况下, 此提交应为工作版本。
而且,如果您喜欢Om,我强烈建议您使用官方教程和“ 零至Om”系列。
翻译自: https://www.javacodegeeks.com/2015/10/clojure-web-development-state-of-the-art-part-2.html