Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 451
mlovic Avatar asked Jul 05 '16 14:07

mlovic


2 Answers

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.

like image 140
superkonduktr Avatar answered Sep 28 '22 07:09

superkonduktr


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.

like image 25
Valentin Waeselynck Avatar answered Sep 28 '22 07:09

Valentin Waeselynck