Это вторая часть моей серии «Clojure web development». Вы можете обсудить первую часть в этой теме Reddit. После прочтения комментариев я должен объяснить два предположения, которые я имел при написании этой серии:
- Делайте вещи понятными для людей из-за пределов Clojure, особенно для разработчиков Java. Вот почему я использую REST / JSON в пользу транзита и компонента в качестве реализации «внедрения зависимости», что можно легко объяснить как эквивалент среды Spring. То же самое относится и к Om, который немного многословен, но, на мой взгляд, его легче понять с самого начала, и он более широк, чем другие обертки React.
- Упрощайте загрузку на компьютере разработчика. Это практическое руководство, и все отдельные шаги были совершены в GitHub. Вот почему я использую MongoDB, которая не может быть лучшим выбором для масштабирования вашего приложения для миллионов пользователей, но идеально подходит для начальной загрузки — без схемы, таблиц, просто вставьте данные и начните работать. Я очень рекомендую беседу о постоянстве с Полглотом из Honza Kral, где он призывает начать с простого и оптимизировать для счастья разработчика в начале проекта.
В предыдущем посте мы загрузили базовое веб-приложение, обслуживающее данные REST, с (на данный момент статичным) интерфейсом Clojurescript, полностью перезагружаемым благодаря перезагруженным repl и figwheel. Вы можете найти окончательную рабочую версию в этой ветке .
Сегодня мы собираемся показать список контактов, хранящихся в MongoDB. Я предполагаю, что у вас установлен MongoDB, если нет — это тривиально с докером .
Обслуживающий список контактов из базы данных
Хорошо, давайте начнем. В бэкэнде нам нужно добавить некоторые зависимости в project.clj:
1
2
3
4
|
:dependencies ... [org.danielsz/system "0.1.9" ] [com.novemberain/monger "2.0.0" ]] |
monger
— это идиоматическая оболочка Clojure для Java-драйвера Mongo, а system
— хороший набор компонентов для различных хранилищ данных, включая Mongo (немного похожий на Spring Data for Spring).
Для взаимодействия с хранилищем данных мне нравится использовать концепцию абстрактного хранилища. Это должно скрыть детали реализации от вызывающего и позволит в будущем переключиться на другой магазин. Итак, давайте создадим абстрактный интерфейс (в Clojure — протоколе) в components/repo.clj
:
1
2
3
4
|
(ns modern-clj-web.component.repo) (defprotocol ContactRepository (find-all [ this ])) |
Это необходимо в качестве параметра, позволяющего среде выполнения Clojure отправлять правильную реализацию этого хранилища. Монго реализация с Monger действительно проста:
01
02
03
04
05
06
07
08
09
10
11
12
|
(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, созданным системной библиотекой, которая добавляет клиента Mongo под ключом:db
- Поскольку этот компонент не имеет состояния, нам не нужно реализовывать интерфейс
component/Lifecycle
, но он все еще может быть подключен в системе, как типичный компонент, учитывающий жизненный цикл. - Требование
monger.json
добавляет поддержку сериализации JSON для типов Mongo (например,ObjectId
)
Хорошо, теперь пришло время использовать наш новый компонент в конечной точке / example.clj:
1
2
3
4
5
6
7
8
|
(: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
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
(: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, сделаем его зависимым от репозитория, который сам по себе является зависимостью примера конечной точки.
И, наконец, нам нужно настроить :mongo-uri
конфигурации :mongo-uri
в config.clj
:
1
2
3
|
(def environ {:http {:port (some-> env :port Integer.)}} :mongo-uri "mongodb://localhost:27017/contacts" }) |
Чтобы проверить, работает ли он нормально, перезапустите repl, введите (go)
раз и сделайте GET по адресу http: // localhost: 3000 / contacts .
1
2
|
curl http: //localhost:3000/contacts [] |
Итак, мы получили пустой список, так как у нас нет данных в базе данных Mongo. Давайте добавим некоторые с консоли Монго:
1
2
3
4
5
|
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" }); |
И, наконец, наша конечная точка должна вернуть эти две записи:
1
2
|
curl http: //localhost:3000/contacts [{ "lastname" : "Pacino" , "firstname" : "Al" , "_id" : "56158345fd2dabeddfb18799" },{ "lastname" : "Depp" , "firstname" : "Johnny" , "_id" : "56158355fd2dabeddfb1879a" }] |
Милая! Опять же — в случае каких-либо проблем, проверьте этот коммит .
Получение контактов из ClojureScript
На этом шаге мы получим контакты с помощью вызова AJAX на нашем интерфейсе ClojureScript. Как обычно, для начала нам нужно немного зависимостей в project.clj
:
1
2
3
4
5
|
:dependencies ... [org.clojure/clojurescript "1.7.48" ] [org.clojure/core.async "0.1.346.0-17112a-alpha" ] [cljs-http "0.1.37" ] |
ClojureScript уже должен быть виден при использовании figwheel
, но всегда лучше явно figwheel
конкретную версию. cljs-http
является HTTP-клиентом для ClojureScript, а core.async
предоставляет средства для асинхронной связи в модели CSP, особенно полезной в ClojureScript. Посмотрим, как это работает на практике.
Чтобы сделать AJAX-вызов, нам нужно вызвать методы из cljs-http.client
, поэтому давайте добавим это в core.cljs
:
1
2
3
4
|
(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 — использование обратных вызовов или обещаний. Способ core.async
заключается в использовании каналов. Это делает ваш код более похожим на последовательность синхронных вызовов, и его легче рассуждать. Таким образом, функция http/get
возвращает канал, на котором публикуется результат при получении ответа. Чтобы получить это сообщение, нам нужно прочитать с этого канала, используя <!
функция. Поскольку это блокировка, нам также нужно окружить этот вызов макросом go
, как в языке go . Таким образом, правильный способ получения контактов выглядит так:
1
2
3
4
5
6
7
|
(: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 , который помогает вычислять разность между текущим и обновленным представлениями.
Среди многих оболочек React в ClojureScript мне нравится использовать Om с ом-инструментами, чтобы уменьшить детализацию. Давайте введем это в наш project.clj
:
1
2
3
4
|
:dependencies ... [org.omcljs/om "0.9.0" ] [prismatic/om-tools "0.3.12" ] |
Чтобы отобразить компонент «hello world», нам нужно добавить код в core.cljs
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
(: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 ( <div id="main"/>
из index.html
). Компонент app
просто отображает значение :message
, поэтому вы должны увидеть «hello from om». Если у вас запущен fighweel
, вы можете изменить значение сообщения, и оно должно быть обновлено немедленно.
И, наконец, давайте представим наши контакты с Om:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
(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
в глобальном состоянии. И самая сложная часть — мы используем метод жизненного цикла will-mount
для получения контактов с сервера, когда компонент app
собирается смонтировать в DOM.
Опять же, в этом коммите должна быть рабочая версия на случай каких-либо проблем.
И если вам понравился Om, я настоятельно рекомендую официальные учебные пособия и серию Zero to Om .
Ссылка: | Веб-разработка Clojure — состояние дел — часть 2 от нашего партнера JCG Петра Ягельского в блоге Full JVM по разработке стека… |