Статьи

Clojure веб-разработка — состояние искусства

Уже больше года я знакомлюсь с Clojure, и чем больше я в него погружаюсь, тем больше он становится языком . Как только вы победите «страх скобок», все остальное будет иметь значение: инструменты, сообщество, хорошие инженерные практики. Настало время убедить других. В этой статье я попытаюсь пройтись по простому веб-приложению с нуля, чтобы показать ключевые инструменты и библиотеки, которые использовались для разработки с Clojure в конце 2015 года.

Примечание для Clojurians : Этот материал довольно прост и может быть полезен для вас, если вы уже немного знаете Clojure, но никогда не делали ничего большего, чем приложение hello world.

Примечание для разработчиков Java : этот материал показывает, как заменить Spring, Angular, grunt, live-reload набором инструментов и библиотек Clojure и небольшим количеством кода.

  • Репо с финальным кодом и отдельными шагами здесь .

начальная загрузка

Я думаю, что все согласились с тем, что этот компонент является отраслевым стандартом для управления жизненным циклом приложений Clojure. Если вы являетесь разработчиком Java, вы можете думать об этом как о замене Spring (DI) — вы объявляете зависимости между «компонентами», которые разрешаются при «системном» запуске. Поэтому вы просто говорите: «Моему компоненту нужен пул хранилища / базы данных», а библиотека компонентов «внедряет» его для вас.

Для простоты я хотел бы начать с шаблона веб-приложения воздуховодов . Это хорошее приложение для начинающих компонентов, основанное на 12-факторной философии. Итак, начнем с этого:

lein new duct clojure-web-app +example

Параметр +example указывает duct создать пример конечной точки с маршрутами HTTP — это было бы полезно. Чтобы завершить загрузку, запустите lein setup clojure-web-app .

Хорошо, давайте углубимся в код. Код, связанный с компонентами и инъекциями, должен находиться в файле system.clj :

01
02
03
04
05
06
07
08
09
10
(defn new-system [config]
  (let [config (meta-merge base-config config)]
    (-> (component/system-map
         :app  (handler-component (:app config))
         :http (jetty-server (:http config))
         :example (endpoint-component example-endpoint))
        (component/system-using
         {:http [:app]
          :app  [:example]
          :example []}))))

В первом разделе вы создаете экземпляры компонентов без зависимостей, которые разрешаются во втором разделе. Таким образом, в этом примере для компонента «http» (сервера) требуется «приложение» (абстракция приложения), которое, в свою очередь, внедряется с «примером» (фактические маршруты). Если вашему компоненту нужны другие, вы можете получить их по именам (а именно: по ключевым словам Clojure).

Чтобы запустить систему, вы должны запустить REPL — интерактивную среду, работающую в контексте вашего приложения:

lein repl

После просмотра подсказки наберите (go) . Приложение должно запуститься, вы можете посетить http: // localhost: 3000, чтобы увидеть пример страницы.

Огромным преимуществом использования компонентного подхода является то, что вы получаете полностью перезагружаемое приложение. Когда вы изменяете буквально все что угодно — конфигурацию, конечные точки, реализацию, вы можете просто набрать (reset) в REPL, и ваше приложение будет обновлено с помощью кода. Это особенность языка, никакой JRebel, Spring-reloaded не требуется.

Добавление конечной точки REST

Хорошо, на следующем шаге давайте добавим некоторую базовую конечную точку REST, возвращающую JSON. Нам нужно добавить 2 зависимости в файл project.clj :

1
2
3
4
:dependencies
 ...
  [ring/ring-json "0.3.1"]
  [cheshire "5.1.1"]

Ring-json добавляет поддержку JSON для ваших маршрутов (в кольце это называется промежуточным программным обеспечением), а cheshire — это анализатор Clojure JSON (как Jackson в Java). Изменение проектных зависимостей, если это одна из немногих задач, требующих перезапуска REPL, поэтому нажмите CTRL-C и снова введите lein repl .

Чтобы настроить промежуточное программное обеспечение JSON, мы должны добавить wrap-json-body и wrap-json-response непосредственно перед wrap-defaults в system.clj :

1
2
3
4
5
6
7
8
9
(:require
 ...
 [ring.middleware.json :refer [wrap-json-body wrap-json-response]])
 
(def base-config
   {:app {:middleware [[wrap-not-found :not-found]
                      [wrap-json-body {:keywords? true}]
                      [wrap-json-response]
                      [wrap-defaults :defaults]]

И, наконец, в endpoint/example.clj мы должны добавить некоторый маршрут с ответом JSON:

1
2
3
4
5
6
7
8
(:require
 ...
 [ring.util.response :refer [response]]))
 
(defn example-endpoint [config]
  (routes
    (GET "/hello" [] (response {:hello "world"}))
    ...

Перезагрузите приложение с помощью (reset) в REPL и протестируйте новый маршрут с помощью curl :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
curl -v http://localhost:3000/hello
 
< HTTP/1.1 200 OK
< Date: Tue, 15 Sep 2015 21:17:37 GMT
< Content-Type: application/json; charset=utf-8
< Set-Cookie: ring-session=37c337fb-6bbc-4e65-a060-1997718d03e0;Path=/;HttpOnly
< X-XSS-Protection: 1; mode=block
< X-Frame-Options: SAMEORIGIN
< X-Content-Type-Options: nosniff
< Content-Length: 151
* Server Jetty(9.2.10.v20150310) is not blacklisted
< Server: Jetty(9.2.10.v20150310)
<
* Connection #0 to host localhost left intact
{"hello": "world"}

Оно работает! В случае каких-либо проблем вы можете найти рабочую версию в этом коммите .

Добавление внешнего интерфейса с figwheel

Кодирование бэкенда в Clojure великолепно, но как насчет внешнего интерфейса? Как вы, возможно, уже знаете, Clojure можно скомпилировать не только в байт-код JVM, но и в Javascript. Это может показаться знакомым, если вы использовали, например, Coffeescript. Однако философия ClojureScript заключается не только в том, чтобы обеспечить некоторый синтаксический сахар, но и в улучшении вашего цикла разработки с помощью великолепных инструментов и полностью интерактивной разработки. Посмотрим, как этого добиться.

Лучший способ представить ClojureScript в проекте — это figweel . Сначала давайте добавим плагин и конфигурацию Fighweel в project.clj :

1
2
3
:plugins
   ...
   [lein-figwheel "0.3.9"]

И конфигурация cljsbuild:

1
2
3
4
5
6
7
8
:cljsbuild
    {:builds [{:id "dev"
               :source-paths ["src-cljs"]
               :figwheel true
               :compiler {:main       "clojure-web-app.core"
                          :asset-path "js/out"
                          :output-to  "resources/public/js/clojure-web-app.js"
                          :output-dir "resources/public/js/out"}}]}

Вкратце это говорит компилятору ClojureScript, что нужно взять исходники из src-cljs с поддержкой figweel и поместить полученный JavaScript в файл resources/public/js/clojure-web-app.js . Поэтому нам нужно включить этот файл в простую HTML-страницу:

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<head>
</head>
<body>
  <div id="main">
  </div>
  <script src="js/clojure-web-app.js" type="text/javascript"></script>
</body>
</html>

Для обслуживания этого статического файла нам нужно изменить некоторые значения по умолчанию и добавить соответствующий маршрут. В system.clj измените api-defaults на site-defaults как в разделе require, так и в функции base-config . В example.clj добавьте следующий маршрут:

1
(GET "/" [] (io/resource "public/index.html")

Снова (reset) в окне REPL следует перезагрузить все.

Но где наш исходный файл ClojureScript? Давайте создадим файл core.cljs в core.cljs src-cljs/clojure-web-app :

1
2
3
4
5
(ns ^:figwheel-always clojure-web-app.core)
 
(enable-console-print!)
 
(println "hello from clojurescript")

Откройте другой терминал и запустите lein fighweel . Он должен скомпилировать ClojureScript и вывести «Prompt покажет, когда figwheel подключится к вашему приложению». Откройте http://localhost:3000 . Окно Fighweel должно подсказать:

1
2
To quit, type: :cljs/quit
cljs.user=>

Тип (js/alert "hello") . Boom! Если все работает, вы должны увидеть и предупредить в своем браузере. Откройте консоль разработчика в вашем браузере. Вы должны увидеть hello from clojurescript напечатанного на консоли. Измените его в core.cljs на (println "fighweel rocks") и сохраните файл. Без перезагрузки страницы вы должны увидеть обновленное сообщение. Figweel скалы! Опять же, в случае каких-либо проблем, обратитесь к этой фиксации .

В следующем посте я покажу, как извлекать данные из MongoDB, обрабатывать их с помощью REST для более широкого использования и писать компоненты ReactJs / Om для их визуализации. Будьте на связи!