Where to put database access/functionality in clojure application?
I'm writing a small Clojure application which has a lot of interaction with a MongoDB database with 2-3 different collections.
I come from a OOP/Ruby/ActiveRecord background where standard practice is to create one class per data model and give each one access to the database. I've started doing the same thing in my clojure project. I have one namespace per "data model" and each has its own database connection and CRUD functions. However, this doesn't feel very functional or clojure-like, and I was wondering if there is a more idiomatic way of doing it, such as having a data
or database
namespace with functions like get-post
, and limiting access to the database to only that namespace.
This seems like it would have the benefit of isolating the database client dependency to just one namespace, and also of separating pure functions from those with side effects.
On the other hand, I would have one more namespace which I would need to reference from many different parts of my application, and having a namespace called "data" just seems odd to me.
Is there a conventional, idiomatic way of doing this in Clojure?
A nice and, arguably, the most idiomatic (scored 'adopt' on the Clojure radar) way to manage state in a Clojure app is that proposed by Stuart Sierra's great Component library. In a nutshell, the philosophy of Component is to store all the stateful resources in a single system
map that explicitly defines their mutual relationship, and then to architect your code in such a way that your functions are merely passing the state to each other.
Connection / environment access
One part of your system will be to manage the 'machinery' of your application: start the web server, connect do data stores, retrieve configuration, etc. Put this part in a namespace separate namespace from your business logic (your business logic namespaces should not know about this namespace!). As @superkondukr said, Component is a battle-tested and well-documented way to do this.
The recommended way to communicate the database connection (and other environmental dependencies for that matter) to your business logic is via function arguments, not global Vars. This will make everything more testable, REPL-friendly, and explicit as to who depends on whom.
So your business logic functions will receive the connection as an argument and pass it along to other functions. But where does the connection come from in the first place? The way I do it is to attach it to events/requests when they enter the system. For instance, when you start your HTTP server, you attach the connection to each HTTP request coming in.
Namespace organization:
In an OO language, the conventional support for data is instances of classes representing database entities; in order to provide an idiomatic OO interface, business logic is then defined as methods of these classes. As Eric Normand put it in a recent newsletter, you define your model's 'names' as classes, and 'verbs' as methods.
Because Clojure puts emphasis on plain data structures for conveying information, you don't really have these incentives. You can still organize your namespaces by entity to mimick this, but I actually don't think it's optimal. You should also account for the fact that Clojure namespaces, unlike classes in most OO languages, don't allow for circular references.
My strategy is: organize your namespaces by use case .
For example, imagine your domain model has Users and Posts. You may have a myapp.user
namespace for Users CRUD and core business logic; similarly you may have a myapp.post
namespace. Maybe in your app the Users can like Posts, in which case you'll manage this in a myapp.like
namespace which requires both myapp.user
and myapp.posts
. Maybe your Users can be friends in your app, which you'll manage in a myapp.friendship
namespace. Maybe you have a small backoffice app with data visualization about all this: you may put this in a myapp.aggregations
namespace for example.
上一篇: 版本vs构建在Xcode中