Under the hood

Our docs are on GitHub, and all contributions are welcome. Suggest an edit or report a problem.

When relying on a tool for mission critical applications, you feel more comfortable if you understand how that tool works.

The goal of this document is to give you a high level understanding of how Edge is built. By understanding this, the "magic" disappears and you can understand what Edge is doing. If you don’t understand all of this, don’t worry. It isn’t necessary for using Edge, and your understanding will improve as you explore the other sections of documentation.

System

Edge provides automatic integration with your "system". In Edge, a system is an integrant system, but could be extended to support other libraries also. This integration is basically just locating your system, and prepping it.

This integration provides you with leverage, because the production and development systems find and run your system. Commonly there’s a lot of boilerplate code involved in locating and configuring your system, and providing that to development and production functions. By providing only the config.edn (in green) with the :ig/system key in it, all of the Edge components (yellow) are able to build on top of it.

diag d2127f79bf64645f28488c36410ffc2b

edge.system reads "config.edn" and gets the :ig/system key, this system has integrant/prep run against it.

Both "lib.app.prod" and "lib.app.dev" use "edge.system" to retrieve the system for starting. "lib.app.prod" will start it when run as a main. "lib.app.dev" will start it on demand when you call (go), (reset) etc.

System flow

The heavy lifting in the system is handled by integrant, of which the author has a great presentation explaining how it works. Edge provides some convenient integrant components for common JUXT libraries (Yada, bidi) for user convenience, but Edge has no dependency on them at all.

The config.edn file is read, and the :ig/system key is taken out.

diag 2ecce3b98535e832480c97e0ef2087d6

Calling integrant/init on a system will first find the dependencies between the components based on their use of #ig/ref.

Here’s an example http system and the resulting dependency graph:

{:db {:url "db://db"}
 :login-handler {:db #ig/ref :db}
 :logout-handler {} ;; No configuration required
 :router [["/login" #ig/ref :login-handler]
          ["/logout" #ig/ref :logout-handler]]
 :web-server {:handler #ig/ref :router
              :port 8000}}
diag 07391e33026a13918c47596220c57d30

The lines indicate a dependency from the source to destination. For example, the web-server depends on the router. This dependency graph is used by integrant to start each component in order, substituting the component’s dependencies as it goes. In our example, a start order might be [:db :logout-handler :login-handler :router :web-server] (bottom to top from the graph).

To start a component, integrant calls integrant/init-key with the key of the component and it’s value e.g. (init-key :db {:url "db://db"}). The return value from init-key replaces the #ig/ref in their configuration. Essentially, :login-handler will be called like so: (init-key :login-handler {:db (init-key :db {:url "db://db"})}).

Dev

The lib.app.dev package wraps several other components

diag 60a93ce595848535268c5aca43a8e32f

It uses these other components to provide a complete development package.

The simple ones like integrant-repl, and spyscope are used to provide direct access to their features. integrant-repl powers the system (reset), (go) functions in dev-extras. spyscope provides utilities for tracing like #spy/p.

More novelty can be found in the dev.logging package which provides filtered logs with a tailored development "logback.xml". The logback.xml gets the prefix for your project from "log_dev_app.properties" which is what is used to filter logs into "application" and "everything". Dev logging is loaded automatically as a dependency of "lib.app.dev" to provide the overall development experience. There’s far more detail about the behavior of dev logging in Dev logging.

dev-extras is the Edge development namespace which you :refer :all into your own project. It has some code equivalent to (def reset integrant.repl/reset) so that :refer :all will bring in a select set of vars from other namespaces.

bin/app

This script runs a template which performs tedious project setup that can’t go into a library. It is very small and most things go in Edge libraries so that updating Edge will update your project.

bin/app uses clj-new (like many Clojure templates).

The key tasks it performs are:

  • Setting up a default ClojureScript/Sass build config (if --cljs is used)

  • Providing an example integrant component to start modifying

  • Creating your dev.clj that requires dev-extras

  • Creating your log_dev_app.properties containing your namespace prefix

Last updated 2019-06-06 18:51:50 +0100