Веб-приложения на Clojure

На Хабре не так уж и много статей о Clojure, и это печально, намерен это исправить. Ниже я расскажу об отличном на мой субъективный взгляд инструменте — языке программирования Clojure и его библиотеках для создания веб-приложений.

В этой статье не будет сравнения Clojure с другими языками, так как отталкиваясь от скромного опыта сравнивать не с чем.

Основная информация о Clojure

Clojure — это мультипарадигмальный язык программирования общего назначения поощряющий функциональное программирование. Основой этого языка и его синтаксиса является Lisp и его S-expressions. В отличие от Lisp'a Clojure включает и другие типы данных (коллекции), такие как: векторы, ассоциативные массивы, множества и очень удобные в использовании ключевые-слова. Код Clojure компилируется в JVM байткод, что позволяет развертывать приложения на большом количестве платформ и само-собой использовать все доступные Java библиотеки.

В интернетах полно информации о структурах Clojure, поэтому я пропущу описание оных.

Комюнити

Нельзя не сказать о сложившимся вокруг этого языка программирования сообществе, которое не столь огромно, но очень дружелюбно к новичкам и не только, они с готовностью помогают решать возникшие проблемы. Имеется большое количество видеоматериалов по самым разным аспектам использования Clojure, только вот практически все они на английском языке (рай для самореализации переводчиков). В конце статьи будут ссылки.

Leiningen

Альтернатива Maven. Используется для управления зависимостями проекта, настройками библиотек и глобальными настройками проекта. Предоставляет консольные команды для создания, тестирования, компиляции и запуска приложений.

Проекты на Clojure создаются с помощью команды: $ lein new <название шаблона проекта (app|compojure|luminus...)> <название проекта>
Это создает каталог с проектом Clojure, имеющую все необходимые файлы и каталоги. И конечно же создает файл project.clj в котором и подключаются все библиотеки и выставляются глобальные настройки: библиотек, проекта, компиляции, repl и т.п…

Пример файла project.clj:
(defproject test "0.1.0-SNAPSHOT"

  :description "Описание"
  :url "http://test.ru"

  :dependencies [[org.clojure/clojure "1.7.0"]
                 [selmer "0.8.2"]
                 [com.taoensso/timbre "4.0.2"]
                 [com.taoensso/tower "3.0.2"]
                 [markdown-clj "0.9.67"]
                 [environ "1.0.0"]
                 [compojure "1.3.4"]
                 [ring/ring-defaults "0.1.5"]
                 [ring/ring-session-timeout "0.1.0"]
                 [metosin/ring-middleware-format "0.6.0"]
                 [metosin/ring-http-response "0.6.2"]
                 [bouncer "0.3.3"]
                 [prone "0.8.2"]
                 [org.clojure/tools.nrepl "0.2.10"]
                 [buddy "0.6.0"]
                 [com.novemberain/monger "2.0.1"]
                 [org.immutant/web "2.0.2"]
                 [clojure.joda-time "0.6.0"]]

  :min-lein-version "2.0.0"
  :uberjar-name "test.jar"
  :jvm-opts ["-server"]

  ;;enable to start the nREPL server when the application launches
  ;:env {:repl-port 7001}

  :main test.core

  :plugins [[lein-environ "1.0.0"]
            [lein-ancient "0.6.5"]]

  :profiles {:uberjar {:omit-source true
                       :env {:production true}
                       :aot :all}

             :dev {:dependencies [[ring-mock "0.1.5"]
                                  [ring/ring-devel "1.3.2"]
                                  [pjstadig/humane-test-output "0.7.0"]]

                   :repl-options {:init-ns test.core}
                   :injections [(require 'pjstadig.humane-test-output)
                                (pjstadig.humane-test-output/activate!)]

                   :env {:dev true}}})



Ring

Ring — представляет из себя слой абстракции над HTTP, предоставляя взаимодействие с ним через простой API. Очень успешно применяется при создании модульных приложений.
Примеры использования, можно увидеть на их странице github (ссылка в конце статьи). Я же использую еще одну абстракцию над Ring, которая на мой взгляд более упрощает работу с маршрутами Ring и называется Compojure.

Compojure

Библиотека для маршрутизации Ring, с её помощью можно удобно упаковывать маршруты и использовать их в handler'e проекта.

Привожу простой пример:
(defroutes auth-routes
  ; Очистка сессии
  (GET "/logout"

       ; Мы можем передать запрос контроллеру
       ; полностью
       request
       (-> logout-controller))

  ; Обработчик авторизации через POST запрос
  (POST "/login"

        ; Или передать определенные параметры
        [login password]
        (login-controller login password))

  ; Авторизация
  ; Указан GET запрос и можно вызвать
  ; представление страницы напрямую
  ; или упаковать все в контроллер 
  ; из которого будет вызвана функция
  ; представления страницы
  (GET "/login"
       request
       (view/login-page)))



Такое тоже возможно:
(defroutes users-routes

  ; Страница просмотра профиля
  (GET "/profile/:login"

       ; В request в :params уже будет доступно
       ; значение login с ключом указанным выше :login
       request
      
       ; Синтаксический сахар под названием ->
       ; передает первый входящий аргумент
       ; функции обработчику.
       (-> profile-view-controller-GET)))



Не стану приводить пример request'a так как вы все прекрасно понимаете как он выглядит. На этом о Compojure закончено.

Buddy

Библиотека для авторизации и аутентификации пользователей. Упаковывает сессии авторизованных пользователей в HTTP заголовок в свой backend, имеет функции для шифрования паролей и т.п…

Пример шифрования пароля:
(buddy.hashers/encrypt "qwerty")


Пример функции авторизации из моего проекта:
(defn login-controller
  "Авторизация пользователя"
  [request]
  (let [
        ; Получить данные из формы
        form {:login (get-in request [:form-params "login"])
              :password (get-in request [:form-params "password"])}
        
        ; Проверить данные на валидность
        validate (bouncer/validate form valid/login-validator)
        
        ; Обработать ошибки
        errors (first validate)
        return-errors (fn [message]
                        (util/return-messages
                         view/login-page
                         :error-message message
                         :data validate))]

    ; Ошибки при валидации
    (if-not errors

      ; Наличие пользователя с указанным логином
      (if (true? (db/user-exist? {:login (:login form)}))

        ; Получить структуру пользователя
        (let [user (db/get-user {:login (:login form)}
                                [:password])]

          ; Соответсвие паролей
          (if (hashers/check (:password form) (:password user))
            (do

              ; Обновить :visited
              (db/update-user
               {:login (:login form)}
               {:visited (util/date-time)})

              ; Создать новую сессию
              (util/create-session request (:login form) "/"))

            ; Если пароли не совпали
            (return-errors "Неверный пароль")))

        ; Если логин не найден
        (return-errors "Логин не найден"))

      ; Ошибка при валидации
      (return-errors "Проверьте правильность введенных данных"))))



Так-же Buddy позволяет настраивать доступ к страницам в middleware.

Пример:
(def rules
  [{:pattern #"^/user/edit$"
    :handler authenticated-user}

(defn on-error
  [request response]
  {:status  403
   :headers {"Content-Type" "text/html"}
   :body    (str "Нет доступа к " (:uri request) ".<br>" response)})

(defn wrap-restricted
  [handler]
  (restrict handler {:handler authenticated?
                     :on-error on-error}))

(defn wrap-identity
  [handler]
  (fn [request]
    (binding [*identity* (or (get-in request [:session :identity]) nil)]
      (handler request))))

(defn wrap-auth
  [handler]
  (-> handler
      wrap-identity
      (wrap-authentication (session-backend))))

; И пример middleware base:
(defn wrap-base
  [handler]
  (-> handler
      wrap-dev

      ; Наши правила доступа
      (wrap-access-rules {:rules rules :on-error on-error})

      ; Сама авторизация
      wrap-auth

      ; Сессия
      (wrap-idle-session-timeout
        {:timeout (* 60 30)
         :timeout-response (redirect "/")})
      wrap-formats
      (wrap-defaults
        (-> site-defaults
            (assoc-in [:security :anti-forgery] false)
            (assoc-in  [:session :store] (memory-store session/mem))))
      wrap-servlet-context
      wrap-internal-error
      wrap-uri))



Selmer

HTML шаблонизатор, вдохновленный Django. Позволяет очень гибко работать с данными в HTML шаблонах.

(defn registration-page
  "Страница регистрации пользователя"
  []
  (render "registration.html"
        {:foo [1 2 3 4 5]})))


И сам шаблон:
<ul>
    {% for i in foo %}
    {{i}}
    {% endfor %}
</ul>


Monger

Библиотека для работы с mongodb, вообще Clojure очень удобный инструмент для работы с базами данных, все благодаря его коллекциям. Monger это Clojure MongoDB клиент (суть библиотека), предоставляющий функции, высокого и низкого уровня для взаимодействия с API MongoDB. Библиотека написана весьма лаконично и в то же время предоставляет все необходимое для полноценного использования MongoDB в приложениях. Нельзя не заметить огромный плюс — это очень развернутая и подробная документация на официальном сайте.

Небольшой пример:
(ns test.users.db
  (:require monger.joda-time
            [monger.collection :as m]
            [test.db :refer [db]]))

(def collection "users")

(defn get-user
  "Найти пользователя"
  ([query]
   (m/find-one-as-map db collection query))
  ([query fields]
   (m/find-one-as-map db collection query fields)))

; Пример использования:
; Вернет нужные нам поля
(get-user {:login "test"} [:first-name :last-name])

; Вернет весь документ
(get-user {:login "test"})



Про обилие скобок

Скобок много, нужно привыкнуть, но внимательный человек обратит внимание, что их не больше чем фигурных скобок в том-же JavaScript.

Ссылки на описанные библиотеки



Дополнительные ссылки



На этом пожалуй все, в дальнейших статьях если к ним проявится интерес я расскажу про каждую библиотеку в отдельности, про замечательный веб-сервер immutant, ну и конечно же про ClojureScript который удобно использовать при разработке front-end приложений и компилируется он в javascript. Так-же хотелось бы осветить фреймфорк Luminus, который очень сильно помог мне разобраться с веб-разработкой на Clojure. Надеюсь моя, хоть и не всеобъемлющая статья заинтересует вас просмотреть возможности этого замечательного инструмента.

Благодарю, всего вам лучшего!
Share post

Similar posts

Comments 10

    +3
    Я бы хотел добавить, что библиотеки на Clojure довольно компактные по размерам и доступные для понимания. В отличии от, например, Spring из Java код Ring и Compojure приятно читать и он, что удивительно, простой. Мне лично для работы с Clojure помог сайт http://www.4clojure.com/ всем начинающим я рекомендую порешать задачки оттуда.
      +1
      Это точно, пришлось копаться в прожекте на Spring, ощущение что там пишут код ради кода, вначале все «по полочкам разложат», тучу классов хелперов, dao, репозиториев наколбасят, есть несколько разных аннотаций которые делают одно и тоже, но разные типа чтобы понимать для каких мест они используются и тд… в итоге логики самого приложения не видно за всем этим
        +3
        А мне код Spring показался одним из самых продуманных и хорошо структурированных исходников, которые я когда-либо видел. На вкус и цвет товарищей нет, видимо. Сколько я в Spring ни залазил, всегда легко разбирался, что и как там надо сделать.

        Вот про Hibernate могу сказать обратное. Написано вроде неплохо, но очень много кода плохо документировано, по крайней мере было лет 5 назад.
          +1

          А теперь еще http://clojurecourse.by/

          0
          Отлично, спасибо за статью, я сам задумывался о том, что недостаточно информации про clojure(script) на хабре.
          Начал было неспешно писать про то, как готовить изоморфные «фуллстек» приложения на Clojure + ClojureScript + Datomic (Component, Immutant, Om, Weasel, tao, Nashorn, etc...).
          Пока руки не доходят дописать. Плюс само приложение есть желание перепилить на om-next. Надеюсь, что осилю до конца лета.
            0
            Мне было бы интересно прочитать про использование ClojureScript, сравнение с CoffeeScript/JavaScript и, конечно же, впечатление программиста от использования. Так что, автор, если напишите — лично я буду признателен.
              0
              Это вопрос времени, к сожалению в настоящий момент моих познаний в ClojureScript явно не достаточно, чтобы написать полноценную статью и описать в ней что-либо полезное для уважаемых Хабражителей. Следующая статья будет про mongodb а точнее про библиотеку Monger и про её использование в веб-разработке.

              Что касается ClojureScript'а и его сравнения с JavaScript, дабы не плодить здесь ссылок отправил вам сообщением. Для интересующихся — очень содержательно и с юмором о ClojureScript рассказывает Александр Соловьев в своей презентации по ClojureScript.
                0
                Можно тоже ссылочку? Спасибо!
            +1
            Сообщество в слаке clojurians.net (есть активная русскоязычная ветка).

            Only users with full accounts can post comments. Log in, please.