24 January 2016

How I use Component

There have been a few discussions lately on how to structure Clojure applications. In one corner there’s reigning champion Component, and in the other the newcomer Mount.

I come down in favour of Component, but during the discussion I found that the way I currently use Component has diverged from the way Component is advocated in Stuart Sierra’s 2014 Just Enough Structure talk. Rather than contrasting Component and Mount, which has been done by others, I thought I’d instead write about how I use Component differently to Stuart.

Too much structure?

In his talk, Stuart uses a component as a domain model:

(defrecord Customers [db email]) (defn notify [customers name message] (let [{:keys [db email]} customers address (query db ... name)] (send email address message)))

This is a fairly brief example, but it isn’t at all how I would approach the problem.

I’d instead start by staking out the boundaries of the system with protocols. In Stuart’s example the edges of his system are the database and the email gateway, so I’ll write the following protocols:

(defprotocol CustomerDatabase (get-customer-by-name [db name])) (defprotocol EmailSender (send-email [mailer message]))

Since sending emails will work in a similar way regardless of what I’m using on the back end, the EmailSender protocol can be quite generic. The CustomerDatabase protocol, on the other hand, needs to be more specific, since databases can differ significantly. In this case, all I care about is whether I can retrieve customer data by their name.

Rather that creating a new component, I use extend-type to implement the protocols for my existing database and emailer components:

(extend-type DatabaseComponent CustomerDatabase (get-customer-by-name [db name] ...) (extend-type EmailComponent EmailSender (send-email [mailer message] ...))

For EmailComponent , it might make sense to implement the EmailSender protocol inline, inside the defrecord . This is because an EmailComponent is always going to be an EmailSender . However, in the case of DatabaseComponent I’d always favour using extend , because not every DatabaseComponent will be a CustomerDatabase .

Once I’ve defined my protocols, I then consider how much code I can write that’s purely functional. In this example, perhaps I want to email customers about outgoing orders. While sending the email and finding the customer’s information are side-effectful, actually constructing the email is not:

(defn new-order-email [customer order] {:to [(:email customer)] :from "noreply@example.com" :subject "Your order has been sent" :body (format "..." (:number order))})

I find this to be is a common pattern. I often run into cases where I need to pull some data from an external resource, transform it in some way, and then send it off to some output. Usually the transformation can be implemented as a pure function.

Finally, we can bring all these functions together:

(defn notify-customer-of-order [db mailer customer-name order-number] (let [customer (get-customer-by-name db customer-name) order (get-order-by-number db order-number)] (send-email mailer (new-order-email customer order))))

You can probably infer that the get-order-by-number function in the example will be defined similarly to the way I wrote get-customer-by-name .

Also notice that the db and mailer arguments are accessed only with protocol methods. The notify-customer-of-order function doesn’t care what the value of these arguments are, so long as they satisfy the right protocols. When we come to test this function, we can use a mocking library like shrubbery.

Avoiding tool overuse

I think a lot of the problems people have with Component stem from overusing it. Component is a great tool for managing a dependent graph of things that need to be started and stopped in a particular order, but there is a tendency to try and use it for more than that.

This isn’t helped by the resemblence of components to objects. If, like me, you’ve come from a background of object-orientated languages, you’ll be accustomed to environments where everything is an object, and therefore there’s a tendency to want to make everything a component. This is a hard habit to break.

To counter this, I try to ask myself the following question: is this something that has a lifecycle tied to the system? Does it have anything that needs to be initiated at system start, and cleaned up at system stop? If it doesn’t, then it usually doesn’t need to be a component.

When I can, I eliminate side-effects and use pure functions. When I can’t, I use protocols to create clear boundaries. I use components to only handle things that I need to start and stop with the system. I find that this methodology tends to avoid a lot of the complication found in applications that use Component too heavily.