/pathom-datomic

Pathom Connect integration with Datomic

Primary LanguageClojureMIT LicenseMIT

Pathom Datomic

This package includes functions to integrate Datomic with Pathom Connect.

⚠️
Project in alpha state, need volunteers to try it out!

Also, this project uses the reader3 from Pathom, which also still needs production grade testing. I recommend this project for people trying to experiment and help the development of these new pieces, if you need production tested implementations its better to avoid pathom-datomic at this time.

Setup

Currently supported only with deps:

{:deps {com.wsscode/pathom-datomic {:git/url "https://github.com/wilkerlucio/pathom-datomic.git"
                                    :sha     "d1ebb8447342a7b1279dc23bc4ab9942a4d8171b"}}

Integration

Using this integration, you provide the Datomic connection to Pathom, then it loads your Datomic schema and delegates sub queries to Datomic automatically.

The examples in this documentation will be using the Datomic mbrainz sample database.

Example setup:

(ns com.wsscode.pathom.connect.datomic-test
  (:require [clojure.test :refer :all]
            [com.wsscode.pathom.connect :as pc]
            [com.wsscode.pathom.connect.datomic :as pcd]
            [com.wsscode.pathom.connect.datomic.on-prem :refer [on-prem-config]]
            [com.wsscode.pathom.core :as p]))

(def whitelist-attributes
  #{:artist/country
    :artist/gid
    :artist/name
    :artist/sortName
    :artist/type
    :country/name
    :medium/format
    :medium/name
    :medium/position
    :medium/trackCount
    :medium/tracks
    :release/artists
    :release/country
    :release/day
    :release/gid
    :release/labels
    :release/language
    :release/media
    :release/month
    :release/name
    :release/packaging
    :release/script
    :release/status
    :release/year
    :track/artists
    :track/duration
    :track/name
    :track/position})

(def parser
  (p/parser
    {::p/env     {::p/reader               [p/map-reader
                                            pc/reader3
                                            pc/open-ident-reader
                                            p/env-placeholder-reader]
                  ::p/placeholder-prefixes #{">"}}
     ::p/mutate  pc/mutate
     ::p/plugins [(pc/connect-plugin {::pc/register []})
                  ; (1)
                  (pcd/datomic-connect-plugin (assoc on-prem-config
                                                ::pcd/conn conn
                                                ::pcd/whitelist whitelist-attributes}))
                  p/error-handler-plugin
                  p/trace-plugin]}))
  1. Datomic connect integration plugin. The whilelist part is where you decide which attribute are going to be automatically available.

ℹ️
We are using the on-prem config for this tutorial, if you wanna connect using the client api, get the config from com.wsscode.pathom.connect.datomic.client/client-config

This setup will automatically provides entry points for every Datomic attribute marked with :unique true.

🔥
If you add :db/id to the whitelist, you can allow referencing using it, but be careful, if nothing else is done (via plugin or other way) to secure access, this means any user of the API could see any entity.

Example queries we can run:

(parser {}
  [{[:db/id 637716744120508]
    [:artist/name]}])
; => {[:db/id 637716744120508] {:artist/name "Janis Joplin"}}

(parser {}
    [{[:artist/gid #uuid"76c9a186-75bd-436a-85c0-823e3efddb7f"]
      [:artist/name
       {:artist/country [:country/name]}]}])
; => {[:artist/gid #uuid"76c9a186-75bd-436a-85c0-823e3efddb7f"]
;     #:artist{:name "Janis Joplin"
;              :country #:country{:name "United States"}}}

When Pathom sees a Datomic attribute, it will compute the sub-query that should be delegated to Datomic, issue a single pull to fulfil the data, and them walk the results to fulfil any extra data that need to come from other resolvers.

To illustrate this point, let’s add a new resolver to the game and play with it in the query, this resolver will count the number of years that some artist was active, considering the start and end year data points:

(pc/defresolver years-active [env {:artist/keys [startYear endYear]}]
  {::pc/input  #{:artist/startYear :artist/endYear}
   ::pc/output [:artist/active-years-count]}
  {:artist/active-years-count (- endYear startYear)})

; then add it to the connect-plugin definition, as:
; (pc/connect-plugin {::pc/register [years-active]})

Time to play:

(parser {}
  [{[:artist/gid #uuid"76c9a186-75bd-436a-85c0-823e3efddb7f"]
    [:artist/active-years-count]}])
; {[:artist/gid #uuid"76c9a186-75bd-436a-85c0-823e3efddb7f"] #:artist{:active-years-count 27}}

Notice that Pathom was able to automatically figure out the dependencies and compute the appropriated pull request, which in this case was [:artist/startYear :artist/endYear].

Datomic ident values

If you used the Datomic pull syntax, you know this kind of result:

(parser {}
  [{[:artist/gid #uuid"76c9a186-75bd-436a-85c0-823e3efddb7f"]
    [:artist/type]}])
=>
; => {[:artist/gid #uuid"76c9a186-75bd-436a-85c0-823e3efddb7f"] #:artist{:type #:db{:id 17592186045423}}}

; if you are familiar with pull, you know the drill:
(parser {}
  [{[:artist/gid #uuid"76c9a186-75bd-436a-85c0-823e3efddb7f"]
    [{:artist/type [:db/ident]}])
=>
; => {[:artist/gid #uuid"76c9a186-75bd-436a-85c0-823e3efddb7f"] #:artist{:type #:db{:ident :artist.type/person}}}

Not so good, even with the version we get the actual value :artist.type/person, we still have to unwrap that.

To facilitate this common scenario, you can configure the integration and tell Pathom which attributes should behave as ident attributes, this means if you don’t do a sub query on them, Pathom will request the :db/ident and unwrap automatically:

; just the changes to the datomic connect plugin:
(pcd/datomic-connect-plugin (assoc on-prem-config
                              ::pcd/conn conn
                              ::pcd/ident-attributes #{:artist/type}))

; now a different result
(parser {}
  [{[:artist/gid #uuid"76c9a186-75bd-436a-85c0-823e3efddb7f"]
    [:artist/type]}])
=>
; => {[:artist/gid #uuid"76c9a186-75bd-436a-85c0-823e3efddb7f"]
       #:artist{:type :artist.type/person}}

; you can still get the entity data using a sub query:
(parser {}
  [{[:artist/gid #uuid"76c9a186-75bd-436a-85c0-823e3efddb7f"]
    [{:artist/type [:db/id]}]}])
=>
; => {[:artist/gid #uuid"76c9a186-75bd-436a-85c0-823e3efddb7f"] #:artist{:type #:db{:id 17592186045423}}}

Custom queries

To handle custom sets of entities, we recommend using the helpers query-entity and query-entities, provided by this library, examples:

(pc/defresolver artists-before-1600 [env _]
  {::pc/output [{:artist/artists-before-1600 [:db/id]}]}
  {:artist/artists-before-1600
   (pcd/query-entities env
     '{:where [[?e :artist/name ?name]
               [?e :artist/startYear ?year]
               [(< ?year 1600)]]})})

; using the resolver
(parser {}
  [{:artist/artist-before-1600
    [:artist/name
     :artist/active-years-count
     {:artist/country
      [:country/name]}]}])
=>
; #:artist{:artist-before-1600 #:artist{:name "Heinrich Schütz",
;                                       :active-years-count 87,
;                                       :country #:country{:name "Germany"}}}

Note the Datalog query must use the map format. The :find part will be automatically fulfilled by Pathom, considering the sub query requested in the process, this way you don’t need to care ahead of time.

Opening all data

You can enable the integration to pull everything from Datomic, this can be useful on initial development, but be aware this opens the entire Datomic to the external world.

To do this, use ::pcd/whitelist ::pcd/DANGER_ALLOW_ALL!.

Next steps

We need to figure a way to handle security, currently this integration opens too much for most use cases, if you have ideas please let’s talk at #pathom on Clojurians!