Learn when a DAO layer actually helps in Clojure, how to separate domain logic from persistence without building generic repository boilerplate, and why `next.jdbc` is a cleaner modern fit than older JDBC wrappers.
Data Access Object (DAO): A boundary that isolates persistence logic behind a smaller application-facing interface.
DAO is useful when the rest of the system should not know how rows are fetched, updated, or mapped. In Clojure, though, the pattern should usually stay lighter than the object-oriented textbook version. Many systems only need a small namespace or protocol around database access, not a full inheritance hierarchy of repositories and entity managers.
The strongest reason to introduce a DAO layer is not fashion. It is to keep domain code from depending on:
That makes testing easier and gives the codebase one predictable place to change when the persistence model shifts.
next.jdbc Is the Low-Level DefaultOlder examples often use clojure.java.jdbc, but most current Clojure projects prefer next.jdbc as the low-level JDBC library.
1(ns myapp.user-store
2 (:require [next.jdbc.sql :as sql]))
3
4(defprotocol UserStore
5 (find-user-by-id [this user-id])
6 (create-user! [this user-data]))
7
8(defrecord JdbcUserStore [ds]
9 UserStore
10 (find-user-by-id [_ user-id]
11 (sql/get-by-id ds :users user-id))
12 (create-user! [_ user-data]
13 (sql/insert! ds :users user-data)))
This is already enough for many applications:
The point is not ceremony. The point is keeping persistence concerns contained.
A weak DAO layer often exposes only generic CRUD verbs. A stronger one reflects how the application actually works:
find-user-by-emailreserve-invoice-number!list-open-orders-for-customerThat keeps persistence operations aligned with business use rather than with generic table mechanics.
In Clojure, it is easy to overbuild this pattern. If every table gets a giant generic repository abstraction with little domain meaning, the DAO layer becomes harder to understand than plain SQL namespaces would have been.
Prefer:
Avoid:
Another useful DAO responsibility is mapping between storage shape and domain shape. That might mean:
If callers must manually decode every row shape after retrieval, the persistence seam is only half-built.
Once every namespace builds its own queries, persistence rules become hard to change and harder to test.
The DAO exists to express useful data access boundaries, not just to wrap insert, update, and delete.
If callers still need to understand JDBC details or low-level row formats, the DAO has not done enough useful shaping.
Use a DAO boundary when persistence details would otherwise leak into domain code. In Clojure, keep the layer small, prefer next.jdbc for low-level JDBC work, and expose domain-meaningful query functions rather than a generic repository framework. The best DAO is the one that isolates the database without making the codebase more ceremonial than the problem requires.