Use refs for practical in-process coordination problems such as inventory reservation, paired indexes, and workflow snapshots, while leaving durability and distributed truth to external systems.
Refs are a focused tool. They are excellent when a small set of in-memory identities must move together, and they are a poor substitute for a database, durable queue, or distributed lock.
Use them when the coordination problem is local to one JVM and the invariant is important enough to justify a transaction.
| Scenario | Fit | Reason |
|---|---|---|
| Inventory count plus reservation log | Good | Stock and reservation record must agree. |
| Primary map plus secondary index | Good | Both representations must update together. |
| Simulation state with several entities | Sometimes | Useful when entities are separate refs and coordinated moves matter. |
| Single metrics counter | Poor | Atom is simpler. |
| Order ledger for a real business | Poor | Needs durable storage. |
| Work queue with backpressure | Poor | Queue, channel, or broker fits better. |
The line is practical: if losing the state on JVM restart is unacceptable, refs should not be the source of truth.
This example keeps a user map and an email index consistent. Either both refs update, or neither does.
1(def users-by-id (ref {}))
2(def id-by-email (ref {}))
3
4(defn add-user! [user]
5 (dosync
6 (let [{:keys [id email]} user]
7 (when (contains? @id-by-email email)
8 (throw
9 (ex-info "email already used"
10 {:email email})))
11 (alter users-by-id assoc id user)
12 (alter id-by-email assoc email id))))
This is a good ref example because the two refs represent different identities with one invariant: email lookup must point at the same user stored by id.
| Java design pressure | Clojure STM response |
|---|---|
| Need lock ordering across two maps | Put both map updates in one dosync. |
| Need to avoid deadlock | Let STM coordinate conflicts instead of hand-ordering locks. |
| Need to test the invariant | Test the pure validation and update logic around immutable data. |
| Need durable user records | Use a database; refs can still help for local derived state. |
STM can simplify in-memory coordination, but it should not blur the boundary between memory and persistence.