From time to time I’m asked, “How do you organize namespaces in Clojure projects?” The question surprised me at first, because I hadn’t thought about it much. But then I was using Clojure back when the only way to load code was “load-file.”
Most programming languages, especially object-oriented languages, provide strong hints on how to structure your source files. Everything is a class, and (almost) every class is a file. In Clojure, everything (almost) is a function. Functions are much smaller units than classes. So how do we group them?
The important thing to remember about namespaces is that, from the compiler’s point of view, they don’t matter. Namespaces are a convenience for the programmer, to help you avoid name clashes without having to write longer names. There’s no reason why an entire application can’t be defined in a single namespace. Most of Clojure itself is defined in one namespace with over 500 symbols. (Common Lisp has 978 symbols in a single namespace.)
You can think of namespaces as a tool to express something about your application. Here are some ideas to get you started:
- Group functions into namespaces based on type of data they manipulate. For example, functions to manipulate customer data go in the “customer” namespace. This technique is familiar from object-oriented languages, but it has the same limitations: where do you put functions concerning relationships among two or more types? The OO answer would be to make a new name for the relationship. This style leads to a proliferation of small namespaces, which can become a burden.
- Divide a library into a public API namespace and a internal implementation namespace. Or define a high-level API for common cases and a low-level API for more advanced usage.
- Divide an application into namespaces representing architectural layers. You can examine “ns” declarations to prove that each layer calls functions only from the layer below it.
- Divide an application into namespaces representing functional modules, with well-defined contracts for communication between modules.
- Try to separate decision-making code from the code that carries out those decisions. That is, keep your business logic purely functional and free of side-effects, so it is easy to test. You don’t necessarily have to put side-effect code in a separate namespace, but doing so may help keep it cleanly separated.
With all these techniques, the point to remember is that namespaces are there to help you, not to get in your way. If you have a large namespace, you can still divide it up into multiple files.
The one hard-and-fast rule is that you cannot have a circular dependency between namespaces. That is, if namespace A needs to call functions defined in namespace B, then namespace B cannot call functions in namespace A. There is no workaround, it simply can’t be done. In practice, this is rarely a problem. If you encounter a situation where two namespaces are mutually dependent, it’s probably a sign that they should be merged into a single namespace.
In client projects at Relevance (home of Clojure/core) we often end up with one namespace for each aspect of an application — data access, UI, logging, and so on. Then there’s one “main” namespace that depends on all the others and ties it all together.
Update 9/5/2011: Chris Houser wrote a nice answer on StackOverflow about how to split a Clojure namespace over several files.