Our biggest pain point with karras right now deals with
associations/references. We are using defaggregate where appropriate
but for certain collections we use the :reference(s) type. Ideally we
would like to interact with entities with references as if they are like
aggregates (embedded documents) but have karras handle the serialization
of them... For example, here is some pseudo-repl-like code (assume
with-mongo isn't needed):
(defentity Person
[:first-name
:last-name])
(defentity Company
[:name
:employees {:type :references :of Person}
:boss {:type :reference :of Person}])
(def michael (make Person {:first-name "Michael" :last-name "Scott"}))
; notice how this is unsaved
(def pam (create Person {:first-name "Pam" :last-name "Beasly"}) ;
this one is saved
(def dm (create Company {:name "Dunder Mifflin" :boss michael
:employees [pam]))
; this should auto-save the unsaved references, store the ids, but
preserve the real entity records in the :boss and :employees locations
after the save.. So...
(= [pam] (:employees dm)) ; true
(= (fetch-one Person {:last-name "Scott"}) (:boss dm)) ; true
When you fetch entities that have references you generally don't want to
fetch the references. However, a pipeline-able function that loads and
assocs the references (that maybe uses the get-reference fn internally)
would be useful for the times when you do want to references loaded onto
the parent entity:
(def dm-with-boss-loaded (-> (fetch-by-id Company {:_id dm)
(load-references :boss)))
(get-in dm-with-boss-loaded [:boss :last-name]) ; Scott
(def dm-with-all-refs-loaded (-> (fetch-by-id Company {:_id dm)
load-references)) ; fetches and assocs both :boss and :employees
We also played around with the idea of having get-reference memoize the
fetched reference with an atom in the metadata of the parent to prevent
repeat calls to the DB. However, that felt a little dirty....
Many times when we fetch an entity with references we generally don't
need to load them and we just need the id. To keep our functions
consistent though it might be nice to have the references always map to
the appropriate entity type. So...
(def dm-no-refs (fetch-by-id Company {:_id dm))
(:boss Company) ; user.Person{:_id #<ObjectId
4c3ce194fa40c2d7246d7efe>} ; so.. a shell of the Person would be
returned with just the _id
The advantage of this is that you can treat/query entities with
un-loaded references the same as if you would if they were loaded
(assuming all you need is the id). Meta-data could perhaps be added to
the entities indicating if they have been loaded or not.
This is what our ideal API for dealing with references, more or less,
would be. We have somewhat approximated this with some callbacks I
created. The callbacks are just a temporary solution until (if) karras
adds such functionality. (The callback solution falls apart when
aggregates have references though, so this should really be something
more fundamental to karras IMO.) Implementation details aside, how does
such an API sound? Do you see any problems with the proposed API changes?
Sorry for the long-winded message and thanks again,
Ben
Somewhat tangential, but another use case we have WRT references is
being able to store the id in a different name than the reference.
Using the previous example we would store the reference ids as :boss_id
and :employee_ids (but still want to refer to the reference as :boss,
etc). This is because we are using Rails as the front-end for this app
which traditionally stores FKs with the _id suffix. (Yes, the underscore
annoys us to no end on the clojure side of things but since Ruby doesn't
like hyphens it is the only option.) With my current implementation I
take that into account and pivot the reference based on that
convention. I'm not asking, nor suggesting, that karras adopts such a
convention but at some point I'd like to see karras be flexible enough
to allow this. I'm more than willing to provide any needed patches, but
I thought I'd let everyone know so it can be kept in mind as different
implementations are tried out.
-Ben