It uses XTDB by default but you can switch to Postgres: https://biffweb.com/p/how-to-use-postgres-with-biff/
People don't really use ORMs in Clojure, they just write SQL directly and abstract the details from consumers using functions. That said, HoneySQL is a common alternative to writing SQL that makes it a lot less painful (and composable!): https://github.com/seancorfield/honeysql
(ns rads.sql-example
(:require [next.jdbc :as jdbc]
[honey.sql :as sql]
[clojure.string :as str]))
;; DB Table: posts
;; +----+-------+
;; | id | title |
;; +----+-------+
;; | 1 | hello |
;; +----+-------+
(def ds (jdbc/get-datasource (System/getenv "DATABASE_URL")))
(defn row->post [row]
;; This is your optional mapping layer (a plain function that takes a map).
;; You can hide DB details here.
(update row :title str/capitalize))
(defn get-posts [ds]
;; Write a SQL query using Clojure data structures.
(let [query {:select [:*] :from [:posts]}]
;; Run the query.
(->> (jdbc/execute! ds (sql/format query))
;; Convert each raw DB map to a "post" map
(map row->post))))
(println (get-posts ds))
;; => [{:id 1, :title "Hello"}]Edit: Not trying to dismiss your concerns, by the way. In Clojure you can often get away with doing less than you might think so I'm genuinely curious about the critique.
I don't need to define driver boilerplate for every query (or ever have to write it... at all).
Note that I'm not making a value judgement about Python/Django or any other library/framework combination. It's obviously a valid path, but Clojure is a different path. I can assure you there are straightforward solutions to create readable APIs like the Django example with minimal boilerplate, but the approach is fundamentally different from Python/Django.
If you do decide to build something in Clojure and think, "I already know how to do this in Django, why is it missing?", don't hesitate to join the Clojurians Slack and hop into the #beginners channel. There are plenty of people who can help you there.
There's definitely a preference in Clojure for not relying on frameworks, because the current people in the community like to be in control, know what's going on, or do it their own way.
That said, the whole code still ends up being relatively small. So, you kind of end up with a similar amount of total code, but you're much more in control. And if certain things you find too repetitive, you can remove the repetition yourself through many of Clojure's facilities, specifically where they annoyed you.
See: https://github.com/didibus/simple-website-with-posts where I implemented the small website you were talking about, creating posts and seeing them. The whole code is here (minus the CSS): https://github.com/didibus/simple-website-with-posts/blob/ma...
It's 95 loc and that includes the templates. There's no framework.
Thanks, that's neat.
I'm not even talking about the framework part. Just db access. Let's say I have a Posts with a managed_by property that points to a list of User which have a ManagedProfile. In Django's ORM (or any good ORM), I could do:
if post.managed_by.contains(user.managedProfile)...
or I could do:
post.managed_by.add(user.managedProfile)
also all these tables and join tables are generated by just a few lines of model definitions.
I'm still in control. I am writing the code. I get to choose when I do slow and fast stuff. Not having these features isn't "more control" it's less features. :P
I still see the benefits of Clojure, though!
And I agree with you, it's less features, and maybe it would be nice to have something similar in Clojure, but there's a reason the feature doesn't feel as needed, and nobody bothered building it.
In OO langs, one of the major pain points the ORM solves is mapping the result back into an Object. Otherwise, you have to manually go:
post = new Post();
post.title = queryResult[1];
post.content = queryResult[2];
...
In fact, some ORM keep to that only, I think are normally called micro-ORMs. All they do is data mapping to/from between the DB and your object model.In Clojure, you don't have this pain point, the DB query returns a list of maps, and you work with those maps directly.
I suspect this is the main reason why no one bothers implementing an ORM-like in Clojure.
That means, for your example, you would create a function that queries the DB to check if a post is managed by a particular user. And you'd call that for your condition:
(if (is-managed-by? post-id user-id) ... ...)
Or to add one you'd do something similar, create a function that adds a user to manage a post: (add-manager-to-post post-id user-id)
You're working directly with IDs, because there are no Objects here. You're working with data directly, and that data is similar to the data in your DB, the representation is much closer between what your code uses and the DB.That means, in OO langs, you think of your state as being those object models, and then you try to sync them back/forth to the DB, after you've mutated it a bunch, you call .save() on it for example. But in Clojure, you think of your state as the DB itself, if you want to change state, you just run a query on the DB to change that state directly, you don't modify some in-memory model and then try to sync that back to the DB.
Some will say this is a good thing (and as a very experienced engineer I will agree) but the barrier to entry for a newbie is quite high in this regard. You have all the rope to hang yourself with - and you will. Especially if you are on a team full of lots of interns and junior folks.
webserver: ring-jetty9-adapter[0]
routing: reitit[1]
html templates: hiccup[2]
never used an ORM, but happily used HugSQL for making composable queries[3].
[0]https://github.com/sunng87/ring-jetty9-adapter [1]https://github.com/metosin/reitit [2]https://github.com/weavejester/hiccup [3]https://www.hugsql.org/
I have no doubt that XTDB is good, it is just surprising as a first choice and is not going to be easy to get up and running compared to the conventional alternatives
(Is there even a hosted offering for XTDB? I like that it's open source but seems that you will have to grapple with all this stuff including a Kafka cluster https://docs.xtdb.com/guides/starting-with-aws.html)