Notes on Component (Part 1)

Stuart Sierra’s Component library for Clojure took awhile for me to really understand. Hence these notes.

Component provides four things:

  1. Start/Stop functionality
  2. Code reloading
  3. Declarative dependencies
  4. No global state

Other approaches like Mount provide #1 and #2, and infer #3 at run-time from the relationships between namespaces. However, they use global state.

I think my main difficulty was the documentation focusses on “Components,” as opposed to focusing on “Systems.”

Systems

A System is defined in terms of its components (ignoring the dependency bit for now):

The workflow is

  • Create the “system” in a non-running state with (make-system)
  • Start the “system” by calling (component/start) on it
  • Your application here
  • Eventually, Stop the “system”

Of course, the following code won’t work yet, since we haven’t defined any of the components, but the third line shows how you access a particular component in the system; (:config system-started) returns the “Config” component.

First Component

Let’s now turn to defining the first component, :logging. Part of the beauty of Component is that you can start with extremely simple, naive implementations to get your whole system built, and then replace them later

This logger will just count the number of log lines written, and print it on “stop:”

So we’ve defined a very simple component. Let’s take a look at what’s happening here…

(make-system) creates some abstract thing which we can treat as a map, extract our :logging component, and treat it like a map.

We still need to do one thing, and that’s actually make our Logging do something (like, you know, log things…)

And this is how we use it… We extract the Logging component from the system, then call the Logging-Protocol method “log-text” with the Component as the first parameter…

Now, that’s a LOT of code to print “Hello World” to the screen. But we needed to get this far to show the next important piece of the puzzle, which is dependencies.

Second Component & Dependencies

Let’s define a Component for Configuration; specifically whether we want logging to go to a file or to the screen. Again, this is a very naive implementation for purposes of example…

Now that we have two Components, we have to re-define our System. While we’re doing that, we’ll encode that Logging depends on Configuration…

We do that by replacing (init-logging) with (component/using (init-logging) [:config])

So what’s the difference between this and before?

Without Dependencies With Dependencies

The key is the highlighted lines on the right; (component/using …) told the system that the Logging component’s (start) and (stop) functions need to be passed the Config Component, which ends up under the key :config.

Now let’s re-write this all so that the Component start function for Logging actually makes use of the Config component…

How System has isolated the dependency

We can use the exact same code now regardless of whether the System has been created with file- or stdout-logging

Leave a Reply