избегать циклической зависимости, когда я получаю доступ к информации о маршруте reitit из обработчика

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

      (ns myapp.user.api
  (:require [reitit.core :as r]))

; define handlers here...

(def router
  (r/router
    [["/user" {:get {:name ::user-get-all
                     :handler get-all-users}}]
     ["/user/:id"
      {:post {:name ::user-post
              :handler user-post}}
      {:get {:name ::user-get
             :handler user-get}}]]))

И эти обработчики затем вызывают службы, которым нужен доступ к информации о маршрутизации ...

      (ns myapp.user-service
  (:require [myapp.user.api :as api]))


; how can I get access to the route properties inside here..?
(defn get-all-users [])
  (println (r/route-names api/router)))

Когда я пытаюсь импортировать маршрутизатор из файла api в службу, у меня возникает проблема с циклическими зависимостями, потому что для API требуется обработчик, который требует обслуживания, поэтому служба не может требовать api.

Как лучше всего избежать этой круговой зависимости? Могу ли я найти значения и свойства маршрутизатора из сервисов?

2 ответа

Я использую шесть общих подходов, чтобы избежать циклических зависимостей в clojure. Все они имеют разные компромиссы, и в некоторых ситуациях одна подходит лучше, чем другая. Я перечисляю их в порядке от того, что я предпочитаю больше всего к тому, что предпочитаю меньше всего.

Я показываю по одному примеру для каждого ниже. Возможно, есть другие способы, о которых я не думал, но, надеюсь, это даст вам некоторые способы осмыслить проблему.

  1. Выполните рефакторинг кода, чтобы удалить часто упоминаемые переменные в новое пространство имен и потребовать это пространство имен из обоих исходных пространств имен. Часто это лучший и самый простой способ. Но это невозможно сделать здесь, потому что корневой обработчик var - это литерал, содержащий var из другого пространства имен.

  2. Передайте зависимое значение в функцию во время выполнения, чтобы не требовалось буквально требовать пространство имен.

      (ns circular.a)

(defn make-handler [routes]
  (fn []
    (println routes)))
      (ns circular.b
  (:require [circular.a :as a]))

(def routes
  {:handler (a/make-handler routes)})

;; 'run' route to test
((:handler routes))
  1. Используйте мультиметоды, чтобы предоставить механизм отправки, а затем определите метод привязки из другого пространства имен.
      (ns circular.a
  (:require [circular.b :as b]))

(defmethod b/handler :my-handler [_]
  (println b/routes))
      (ns circular.b)

(defmulti handler identity)

(def routes
  {:handler #(handler :my-handler)})
      (ns circular.core
  (:require [circular.b :as b]

            ;; now we bring in our handlers so as to define our method implementations
            [circular.a :as a]))

;; 'run' route to test
((:handler b/routes))
  1. Используйте литерал var, разрешенный во время выполнения
      (ns circular.a)

(defn handler []
  (println (var-get #'circular.b/routes)))
      (ns circular.b
  (:require [circular.a :as a]))

(def routes
  {:handler a/handler})

;; 'run' route to test
((:handler routes))
  1. Переместите код в то же пространство имен.
      (ns circular.a)

(declare routes)

(defn handler []
  (println routes))

(def routes
  {:handler handler})

;; 'run' route to test
((:handler routes))
  1. Используйте состояние. Сохраните одно из значений в атоме во время выполнения.
      (ns circular.a
  (:require [circular.c :as c]))

(defn handler []
  (println @c/routes))
      (ns circular.b
  (:require [circular.a :as a]
            [circular.c :as c]))

(def routes
  {:handler a/handler})

(reset! c/routes routes)

((:handler routes))
      (ns circular.c)

(defonce routes (atom nil))

Вы где-то делаете простую ошибку. Мой пример:

      (ns demo.core
  (:use tupelo.core)
  (:require
    [reitit.core :as r]
    [schema.core :as s]
  ))

(defn get-all-users [& args] (println :get-all-users))
(defn user-post [& args] (println :user-post))
(defn user-get [& args] (println :user-get))

; define handlers here...
(def router
  (r/router
    [
     ["/dummy" :dummy]
     ["/user" {:get {:name ::user-get-all
                     :handler get-all-users}}]
     ["/user/:id"
      {:post {:name ::user-post
              :handler user-post}}
      {:get {:name ::user-get
             :handler user-get}}]
     ]))

и используйте здесь:

      (ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test)
  (:require
    [clojure.string :as str]
    [reitit.core :as r]
  ))

(dotest 
  (spyx-pretty (r/router-name router))
  (spyx-pretty (r/route-names router))
  (spyx-pretty (r/routes router))
)

с результатом:

      
*************** Running tests ***************
:reloading (demo.core tst.demo.core)

Testing _bootstrap

-----------------------------------
   Clojure 1.10.3    Java 15.0.2
-----------------------------------

Testing tst.demo.core
(r/router-name router) =>
:lookup-router

(r/route-names router) =>
[:dummy]

(r/routes router) =>
[["/dummy" {:name :dummy}]
 ["/user"
  {:get
   {:name :demo.core/user-get-all,
    :handler
    #object[demo.core$get_all_users 0x235a3fc "demo.core$get_all_users@235a3fc"]}}]]

Ran 2 tests containing 0 assertions.
0 failures, 0 errors.

на основе моего любимого шаблонного проекта

Другие вопросы по тегам