diff --git a/src/darkleaf/di/core.clj b/src/darkleaf/di/core.clj index 011bac8c..2ab0e49f 100644 --- a/src/darkleaf/di/core.clj +++ b/src/darkleaf/di/core.clj @@ -780,3 +780,53 @@ ~@body)) (finally (.close resource#))))))) + +;; может быть сделать 2 арности в одной функции вместо этих 2х функций? + +(defn collect-cache [] + (let [cache (atom {:started? true})] + (fn [registry] + (fn [key] + (if (= ::cache key) + (reify p/Factory + (dependencies [_] + nil) + (build [_ _] + cache) + (demolish [_ obj] + (reset! cache {:started? false}))) + (let [factory (registry key)] + (reify p/Factory + (dependencies [_] + (p/dependencies factory)) + (build [_ deps] + (let [obj (p/build factory deps)] + (when-not (contains? deps ::cache) + (swap! cache assoc + [:key->deps key] deps + [:key->obj key] obj)) + obj)) + (demolish [_ obj] + (p/demolish factory obj))))))))) + +(defn use-cache [cache] + (fn [registry] + (fn [key] + (let [factory (registry key) + cache @cache + cached-deps (cache [:key->deps key]) + cached-obj (cache [:key->obj key])] + (when-not (:started? cache) + (throw (IllegalStateException. "Invalid cache"))) + (reify p/Factory + (dependencies [_] + (p/dependencies factory)) + (build [_ deps] + (if (= cached-deps deps) + cached-obj + (p/build factory deps))) + (demolish [_ obj] + ;;todo: add test + #_(if (identical? cached-obj obj) + nil + (p/demolish factory obj)))))))) diff --git a/test/darkleaf/di/cache_test.clj b/test/darkleaf/di/cache_test.clj new file mode 100644 index 00000000..8d24ed14 --- /dev/null +++ b/test/darkleaf/di/cache_test.clj @@ -0,0 +1,109 @@ +(ns darkleaf.di.cache-test + (:require + [clojure.test :as t] + [darkleaf.di.core :as di])) + +(set! *warn-on-reflection* true) + + +;; мне надоело логгеры писать +;; может уже написать реестр, который логирует все действия? +;; только его нужно передавать извне +;; (atom [ [`key :started] [`key :stopped] ] ) +(defn a + {::di/kind :component} + ;; {::di/stop #(swap! (:log %))} + + [] + [{log ::log}] + {:name :a + :obj (Object.)}) + +(t/deftest a-test + (di/with-open [[main cache] (di/start [`a ::di/cache] + (di/collect-cache)) + [secondary] (di/start [`a] + (di/use-cache cache))] + (t/is (not= nil main secondary)) + (t/is (identical? main secondary)))) + +(defn b + {::di/kind :component} + [{conf "B_CONF"}] + {:name :b + :conf conf + :obj (Object.)}) + +(t/deftest b-test + (di/with-open [[main cache] (di/start [`b ::di/cache] + {"B_CONF" "conf"} + ;; должен быть последним в цепочке, чтобы закешировать все + (di/collect-cache)) + [secondary] (di/start [`b] + (di/use-cache cache))] + (t/is (not= nil main secondary)) + (t/is (identical? main secondary)))) + +(t/deftest b-changed-test + (di/with-open [[main cache] (di/start [`b ::di/cache] + {"B_CONF" "conf"} + (di/collect-cache)) + [secondary] (di/start [`b] + ;; должен быть первым, чтобы его можно было переопределять + (di/use-cache cache) + {"B_CONF" "changed"})] + (t/is (not= nil main secondary)) + (t/is (not (identical? main secondary))))) + +(defn c + {::di/kind :component} + [{a `a, b `b}] + {:name :c + :a a + :b b + :obj (Object.)}) + +(t/deftest c-test + (di/with-open [[main cache] (di/start [`c ::di/cache] + {"B_CONF" "conf"} + (di/collect-cache)) + [secondary] (di/start [`c] + (di/use-cache cache))] + (t/is (not= nil main secondary)) + (t/is (identical? main secondary)))) + +(t/deftest c-changed-test + (di/with-open [[main cache] (di/start [`c ::di/cache] + {"B_CONF" "conf"} + (di/collect-cache)) + [secondary] (di/start [`c] + (di/use-cache cache) + {"B_CONF" "changed"})] + (t/is (not= nil main secondary)) + (t/is (not (identical? main secondary))) + (t/is (= :c + (:name main) + (:name secondary))) + (t/is (identical? (:a main) + (:a secondary))) + (t/is (not (identical? (:b main) + (:b secondary)))) + ;; надо ли проверять? + (t/is (not (identical? (:obj main) + (:obj secondary)))))) + +(t/deftest invalid-cache-test + (let [[main cache :as system] (di/start [`c ::di/cache] + {"B_CONF" "conf"} + (di/collect-cache)) + _ (di/stop system)] + (t/is (thrown? IllegalStateException + (di/start `c + (di/use-cache cache)))))) + +(t/deftest not-recursive-test + (di/with-open [[main cache] (di/start [`c ::di/cache] + {"B_CONF" "conf"} + (di/collect-cache))] + (t/try-expr "must not be recrusive" + (prn-str cache)))) diff --git a/test/darkleaf/di/tutorial/z_memoize_test.clj b/test/darkleaf/di/tutorial/z_memoize_test.clj new file mode 100644 index 00000000..8d6a15c1 --- /dev/null +++ b/test/darkleaf/di/tutorial/z_memoize_test.clj @@ -0,0 +1,53 @@ +(ns darkleaf.di.tutorial.z-memoize-test ;;todo: name + (:require + [clojure.test :as t] + [darkleaf.di.core :as di])) + +(defn connection + {::di/stop #(reset! % :stopped)} + [{url "CONNECTION_URL"}] + (atom :started)) + +(defn migrations + "A long running side effect" + {::di/kind :component} + [{connection `connection}] + #_ + (when (= :stopped @connection) + (throw (IllegalStateException. "Connection is not started"))) + (random-uuid)) + +(defn root + {::di/kind :component} + [{migrations `migrations}] + {:migrations migrations}) + +;; todo: check registry placement + +(t/deftest ok + (let [[cache global-system :as root] + (di/start [::di/cache `root] + {"CONNECTION_URL" "1"} + (di/collect-cache))] + + (with-open [local (di/start `root + (di/use-cache cache))] + (t/is (identical? global-system @local))) + + (with-open [local (di/start `root + (di/use-cache cache) + {"CONNECTION_URL" "1"})] + (t/is (identical? global-system @local))) + + (with-open [local (di/start `root + ;; `use-cache` should be the first registry + (di/use-cache cache) + {"CONNECTION_URL" "2"})] + (t/is (not (identical? global-system @local)))) + + + (di/stop root) + + (t/is (thrown? IllegalStateException + (di/start `root + (di/use-cache cache))))))