Important
|
Extra requirements
Minimal required version of Clojure is 1.11.0, ClojureScript 1.10.866 (due to reliance on Every entity stored in Asami must have the property |
;; in deps.edn:
{:deps {cz.holyjak/fulcro-rad-asami {:mvn/version "RELEASE"}
;; pick ONE of Pathom v2 and Pathom3:
com.wsscode/pathom3 {:mvn/version "2023.01.31-alpha"}
;com.wsscode/pathom {:mvn/version "2.4.0"}
}}
(ns com.example.components.parser ; Pathom 3
(:require
[com.example.model :as your-model]
[com.fulcrologic.rad.attributes :as attr]
[com.fulcrologic.rad.form :as form]
[com.fulcrologic.rad.middleware.save-middleware :as r.s.middleware]
[com.fulcrologic.rad.pathom :as pathom]
[com.fulcrologic.rad.resolvers :as res]
[cz.holyjak.rad.database-adapters.asami :as asami]
[cz.holyjak.rad.database-adapters.asami-options :as aso]))
(def config {::asami/databases {:production {:asami/driver :local #_:mem, :asami/database "fulcro-rad-demo"}}})
;; config should contain the key ::aso/databases, see start-connections docstring
(def asami-connections (asami/start-connections config))
(def automatic-resolvers
(vec
(concat
(res/generate-resolvers your-model/all-attributes)
(asami/generate-resolvers your-model/all-attributes :production))))
(def save-middleware (r.s.middleware/wrap-rewrite-values (asami/wrap-save))) ; the wrap-rewrite-values is optional
;; See examples of this in cz.holyjak.rad.database-adapters.asami-spec
(def parser
(let [env-middleware (-> (attr/wrap-env all-attributes)
(form/wrap-env save/middleware delete/middleware)
(asami/wrap-env (fn [env] asami-connections))
(blob/wrap-env bs/temporary-blob-store {:files bs/file-blob-store
:avatar-images bs/image-blob-store}))]
(pathom3/new-processor {#_"your config here..."} env-middleware []
[automatic-resolvers your-model/custom-resolvers])))
(ns com.example.components.parser ; Pathom 2
(:require
[com.example.model :as your-model]
[com.fulcrologic.rad.attributes :as attr]
[com.fulcrologic.rad.form :as form]
[com.fulcrologic.rad.middleware.save-middleware :as r.s.middleware]
[com.fulcrologic.rad.pathom :as pathom]
[com.fulcrologic.rad.resolvers :as res]
[cz.holyjak.rad.database-adapters.asami :as asami]
[cz.holyjak.rad.database-adapters.asami-options :as aso]
[cz.holyjak.rad.database-adapters.asami.pathom :as asami.pathom))
(def config {::asami/databases {:production {:asami/driver :local #_:mem, :asami/database "fulcro-rad-demo"}}})
;; config should contain the key ::aso/databases, see start-connections docstring
(def asami-connections (asami/start-connections config))
(def automatic-resolvers
(vec
(concat
(res/generate-resolvers your-model/all-attributes)
(asami/generate-resolvers your-model/all-attributes :production))))
(def save-middleware (r.s.middleware/wrap-rewrite-values (asami/wrap-save))) ; the wrap-rewrite-values is optional
;; See examples of this in cz.holyjak.rad.database-adapters.asami-spec
(def parser
(pathom/new-parser {#_"your config here..."}
[(attr/pathom-plugin all-attributes)
(form/pathom-plugin save-middleware (asami/wrap-delete))
(asami.pathom/pathom-plugin (fn [env] asami-connections))]
[automatic-resolvers your-model/custom-resolvers]))
See a complete usage example in https://github.com/fulcrologic/fulcro-rad-demo .
Tip
|
It is better to call asami.core/shutdown when shutting down the backend to ensure that the database files are not unnecessarily large. Though you can likely simply rely on the JVM shutdown hook calling this, which Asami itself registers. (They are created with more space so that data can be inserted quickly and shutdown trims them to the actual content.)
(Beware: as of Asami 2.3.2, when you call shutdown , it will close files but not reset the internal connections map and subsequent asami/start-connections will not re-initialize them properly. So avoid calling shutdown repeatedly during REPLing.)
|
You can set the following on an id attribute (i.e. one with ao/identity? true
and a ao/schema
; ::asami
here is cz.holyjak.rad.database-adapters.asami
):
-
::asami/no-batch?
- by default, all generated id resolvers are batched. In some rare cases you might want to disable that - so set no-batch? to true. (Note: Since Asami runs in-memory, batching most likely doesn’t really matter.) -
::asami/owned-entity?
marks the entity as dependant, i.e. it can only exist as part of another entity - f.ex. an OrderLine can only exist as a part of an Order. It is marked in Asami as "owned" by the parent entity and thus(d/entity parent-entity)
will include its full data and, most importantly, removing it from the:ref
attribute on the parent will delete it fully from the DB (notice that this "cascading delete" is a feature of this adapter, not of Asami itself). -
::asami/wrap-resolve
- a function wrapping the resolver’s resolve function. Example:::asami/wrap-resolve (fn [resolve-fn] (fn [pathom-env input] (println "Running resolve for input" input) (resolve-fn pathom-env input)))
The generated id resolvers use d/entity
to fetch the data. That has the effect of pulling the entity and all nested entities. Normally that is not a problem when you only insert data via save-form etc., because this will break any data into quadruplets and insert even nested entities as top entities. But if you insert data not as quadruplets but as an entity tree as here:
@(d/transact *conn* {:tx-data [{:id [::person/id "ann"]
::person/id "ann"
::person/addresses [{:id [::address/id "a-one"] ; <- nested entity!
::address/id "a-one"
::address/street "First St."}
{:id [::address/id "a-two"]
::address/id "a-two"
::address/street "Second St."}]}]})
then addresses will become nested entities and d/entity
will return person together with the whole value. (Notice that setting nested?
to false on d/entity has no effect here - this option only makes sense with the value true for references to other top entities that you want to pulled whole).
(Notice you can still fetch an address separately with (d/entity conn [::address/id "a-one"])
, thanks to having set that :id
.)
Note
|
WIP A problem with pulling nested entities is that Pathom 3 v.2022.10.19-alpha apparently throws away this nested data. I’m currently looking into this |
Tip
|
To create multiple top-level entities using the entity tx form, this might work (I have not tested it properly): (d/transact conn {:tx-data [{:id "a-one"
:address/id "a-one"
:address/street "First St."}
{:id [:person/id "ann"]
:person/id "ann"
:person/addresses [{:id "a-one"}]}]}) |
To create multiple top-level entities using the entity tx form, this normally works:
(d/transact conn {:tx-data [{:id [:address/id "a-one"]
:address/id "a-one"
:address/street "First St."}
{:id [:person/id "ann"]
:person/id "ann"
:person/addresses [{:id [:address/id "a-one"]}]}]})
When inserting data manually, remember to set :id <ident>
. You can then use it as a lookup ref, e.g. in add: [:db/add [:id <ident>] <prop> <val>]
.
Use functions such as write/retract-entity-txn
and write/delta→txn-map-with-retractions
if you want to make transactions to delete or update entities in a way consistent with
RAD-managed entities.
You can provide a function that is invoked around an autogenerated resolver for an entity by setting ::asami/wrap-resolve
on the ID attribute. Notice that id resolvers typically produce a vector because they are batched.
(defattr id :order/id :uuid
{ao/identity? true
ao/schema :production
:cz.holyjak.rad.database-adapters.asami/wrap-resolve
(fn wrap-resolve [res]
(fn decorated-resolve [env in]
(println "order-id resolver in=" in)
(doto (res env in)
(->> (println "order-id resolver output=")))))})
You can enable debug logging for the adapter. With fulcro-rad-demo or fulcro-template you can configure this in e.g. its dev.edn
:
- {:taoensso.timbre/logging-config {:min-level :info}}
+ {:taoensso.timbre/logging-config {:min-level [[#{"cz.holyjak.rad.database-adapters.asami.*"} :debug]
+ [#{"*"} :info]]}}
The order of multi-valued attributes is lost (Asami returns them as sets, which we turn into a vector).
As of Asami 2.3.2 you cannot create an entity and refer to the entity from another one in the same transaction when using the entity form of tx-data
. If the entity and reference are both created using the quadruplets form ([:db/add <entity> <attr> <val>
) then this works.
We assoc to each persistent entity :id <ident>
(see Asami’s Identity Values) so that we can easily refer to it in statements and from other entities. This is then used as a lookup ref in insert/update statements and in :ref
attributes of other properties. (:ref
attributes stored via a form are automatically translated into this form.) However this property is dissoc-ed when reading. (We could likely also use :db/ident
instead though this has not been tested.)
We store the full ident in the :id
because we cannot be sure that the ID values are globally unique though we know that Fulcro would break if they were not unique for the given entity. (Actually they should be globally unique, being UUIDs, but we might want to support other kinds of IDs in the future that do not guarantee this. We could store the full ident only on such attributes - and maybe we will.)
Quadruples over entities We translate each Fulcro entity diff into a series of quadruplet assertions and retractions and transact these. The reason for this is that we might want to transact multiple new entities that refer to each other in a single transaction (think of saving a form with a subform). I am not sure Asami tempids work for this and in any case they are not ideal because, in the face of no schema, they are just negative integers and then even regular attribute value that happen to be negative integers matching one of the tempids would be replaced with a reference. Instead, we use lookup ids such as {:id [:entity/id #uuid "some-value"]}
but these require that the entity already exists, when used in the entity form, while quadruplets manage to create a new entity and resolve references to it (see quoll/asami#2). One of the disadvantages is that we cannot use the attribute'
or attribute+
shorthand forms.
Limitations and features that are not supported:
-
Currently, IDs must be of the type
uuid
and new entities need this set to atempid
so that the entity is created before being referred to and with correct attributes -
RAD Datomic-like native IDs are not supported yet (see parts of code marked with
FEAT-NAT-IDS
(incomplete), should we ever implement this) -
ao/identities
must have exactly one element
Not tested:
-
Multiple databases / schemas
Run tests: clj -M:pathom3:test:run-tests
Also see the (comment ..)
at the bottom of most -spec
tests for running those in the REPL.
(specification "descr." :focus ...)
then run (fulcro-spec.reporters.repl/run-tests (comp :focus meta))