Datomic has no essential type for entities, so how do you find the user with id X with Datomic?
Finding by internal Datomic entity ID
All entities in datomic gets the attribute :db/id
, which is Datomic's own primary key attribute. You might be surprised that (d/entity db 123)
for an entity id that doesn't exists doesn't return nil.
(require ['datomic.api :as 'd])
(def db (d/db my-datomic-connection))
(def non-existing-id 123123)
(d/entity db non-existing-id)
;; Returns an actual entity
;; Even if there's no entity with id 123123
To actually find an entity by ID, you need to run a query.
;; Assuming an entity with id 123 exists
(d/q '[:find ?eid :in $ ?eid :where [?eid]] db 123)
;; HashSet [[123]]
(d/q '[:find ?eid :in $ ?eid :where [?eid]] db non-existing-id)
;; HashSet [[]]
Also, just for fun, we can create a reproducible example that doesn't depend on having an actual Datomic database around, since Datomic is able to query any data strucutre that consists of a list of lists.
;; This is what an actual Datomic DB actually looks like.
(def my-mock-db [
[123 :name "Test"]
[123 :email "test@test.com"]
[456 :name "Foo"]
[456 :email "Hello"]])
;; A "normal" query, for good measure.
(d/q '[:find ?person
:in $ ?name
:where [?person :name ?name]]
my-mock-db "Test")
;; HashSet [[123]]
(d/q '[:find ?eid :in $ ?eid :where [?eid]] my-mock-db 123)
;; HashSet [[123]]
(d/q '[:find ?eid :in $ ?eid :where [?eid]] my-mock-db 999)
;; HashSet [[]]
However, how do we know that we get an entity of the type we want? Datomic entities doesn't actually have a type, they're just entities with attributes, so we're at a loss.
Using manual "public ID" attributes
I like to have a public ID field per entity type in my system. Again, Datomic entities has no type, so this is all on us, Datomic doesn't care.
I use a string type that's set to unique. Setting it to unique is obviously important. You could also use the built-in UUID type if you want to, but I find it more convenient to just work with strings.
;; Inserting data
(d/transact
my-datomic-conn
[{:person/name "Test"
:person/email "test@test.com"
:person/public-id (str (java.util.UUID/randomUUID))
:db/id (d/tempid :db.part/user}])
;; ...
(d/q
'[:find ?person
:in $ ?pub-id
:where
[?person :person/public-id ?pub-id]]
db
person-id)
Benefits:
- You get an entity type system for free. If you use a Datomic ID, how do you query for people entities only for GET /people/123?
- Datomic entity IDs stay internal, meaning you don't have to worry about possible entity ID changes when you import/export the database.
- You don't have to convert from a string type to a numeric type when you get the entity ID from a URL. Not a very big deal, but since it's a very common use case I'd say it counts.