Адаптер

Весь подпроект web является адаптером между слоем сценариев и веб-сервером. Это можно показать так:

(defn handler [req]
  (let [args   (req->args req)
        result (apply interactor args)
        resp   (result->resp result)]
     resp))

Т.е. обработчик имеет 2 обязанности:

  • преобразование ring запроса в аргументы интерактора, показана как функция req->args
  • преобразование результата интерактора к ring ответу, показана как функция result->resp

Разделим обработчик на составные части. Назовем req->args контроллером, а result->resp респондером.

(defn controller [req]
  (let [arg1 (req->arg1 req)
        arg2 (req->arg2 req)]
    [interactor arg1 arg2])) ;; <1>

(defn responder [result [arg1 arg2]]
  (let [viewmodel (presenter result arg1 arg2)
        html      (template viewmodel)]
    {:status  200
     :headers {}
     :body    html}))

(defn middleware [handler]
  (fn [req]
    (let [[interactor & args] (handler req)
          result              (apply interactor args)]
      (responder result))))

(def handler
  (-> controller
      middleware))

Теперь у нас 4 слабосвязанных компонента.

Middleware и handler существуют в единственном экземпляре, тривиальны и не требуют модульного тестирования.

Контроллеры и респондеры не зависят друг от друга. Вспомним диаграмму связи интерактора, контроллера и презентера. В этом случае респондер - это презентер в терминах диаграммы.

Обратите внимание на <1>, контроллер не вызывает интерактор, а просто возвращает функцию и ее аргументы как данные. Таким образом при тестировании не нужно подменять интерактор, чтобы проверить корректность формирования его аргументов.

Все это делает модульное тестирование очень простым.

В следующих параграфах мы разберем эти компоненты подробнее.