Cyclic Load Dependencyλ︎
A cyclic load dependency is where one namespace requires one or more other namespaces that then require the original namespace, forming a loop when resolve all the required namespaces. When there are cyclic namespace dependencies a warning is returned when evaluating any of the namespaces involved.
A good way to spot cyclic load dependencies it to regularly run a test runner on the code, or even set up a test runner to watch for changes to the file system which then triggers an automatic test run. For example, kaocha --watch
.
Tips on avoiding cyclic load dependenciesλ︎
-
Add comment sections describing the purpose of the different parts of code, creating logical separation of code. Refactor of namespaces is far easier to see and helps ensure related code is kept together.
-
Ensure test and specification code is in its own namespace. Keeping the right level of abstraction in tests and clojure spec test.check can help avoid issues.
-
Use
require
to add dependent namespaces, preferably using a meaningful:as
alias or using:refer
for only the function names used.(:require ,,, :refer :all)
or(use ,,)
expressions can pull in too many other namespaces and cause issues -
A cyclic load dependency error message is a good point to review and refactor the application design.
-
Where two namespaces are interdependent on each other, factor out shared code into a third namespace required by each of the other two. This breaks the dependency between the two original namespaces.
Example - Banking on Clojureλ︎
The practicalli.banking-on-clojure.handler
namespace contains functions that need to access data in the database, so the practicalli.database-access
namespace is required.
(ns practicalli.banking-on-clojure.handler
(:require
;; Web Application
[ring.util.response :refer [response]]
[hiccup.core :refer [html]]
[hiccup.page :refer [html5 include-js include-css]]
[hiccup.element :refer [link-to]]
;; Data access
[practicalli.database-access :as data-access]))
The practicalli.database-access
namespace required the practicalli.banking-on-clojure.specification
namespace to use the specifications for generating data
(ns practicalli.database-access
(:require [next.jdbc :as jdbc]
[next.jdbc.sql :as jdbc-sql]
[next.jdbc.specs :as jdbc-spec]
[practicalli.banking-on-clojure.specification]))
In the practicalli.banking-on-clojure.specification
namespace, a functional specification had been defined for the register-account-holder
function, which required the practicalli.banking-on-clojure.handler
to be required. Even though the specifications testing had logically moved to the database-access namespace, this functional specification remained.
(ns practicalli.banking-on-clojure.specification
(:require
;; Clojure Specifications
[clojure.spec.alpha :as spec]
[clojure.spec.gen.alpha :as spec-gen]
[clojure.spec.test.alpha :as spec-test]
;; Helper namespaces
[clojure.string]
[practicalli.banking-on-clojure.handler :as handler]))
This set of require expressions lead to a cyclic load dependency error.
->
indicate that a namespace requires the namespace it is pointing too.
Removing the require of practicalli.banking-on-clojure.handler
from the practicalli.banking-on-clojure.specification
namespace breaks the cyclic dependency.
Testing the database on CIλ︎
Need to create the tables before the tests can run. - update the schema so the create tables can run without failure, check if table exist and if not create it.