Attaching Resolvers

Schemas start as EDN files, which has the advantage that symbols do not have to be quoted (useful when using the list and non-null qualifiers on types). However, EDN is data, not code, which makes it nonsensical to defined field resolvers directly in the schema.

One option is to use assoc-in to attach resolvers after reading the EDN, but before invoking com.walmartlabs.lacinia.schema/compile. This can become quite cumbersome in practice.

Instead, the standard approach is to put keyword placeholders in the EDN file, and then use com.walmartlabs.lacinia.util/attach-resolvers, which walks the schema tree and makes the changes, replacing the keywords with the actual functions.

(ns org.example.schema
  (:require
    [clojure.edn :as edn]
    [clojure.java.io :as io]
    [com.walmartlabs.lacinia.schema :as schema]
    [com.walmartlabs.lacinia.util :as util]
    [org.example.db :as db]))

(defn star-wars-schema
  []
  (-> (io/resource "star-wars-schema.edn")
      slurp
      edn/read-string
      (util/attach-resolvers {:hero db/resolve-hero
                              :human db/resolve-human
                              :droid db/resolve-droid
                              :friends db/resolve-friends})
      schema/compile))

The attach-resolvers step occurs before the schema is compiled.

Resolver Factories

There are often cases where many fields will need very similar field resolvers. A second resolver option exist for this case, where the schema references a field resolver factory rather than a field resolver itself.

In the schema, the value for the :resolve key is a vector of a keyword and then additional arguments:

{:queries
 {:hello {:type String
          :resolve [:literal "Hello World"]}}}

In the code, you must provide the field resolver factory:

(ns org.example.schema
  (:require
    [clojure.edn :as edn]
    [clojure.java.io :as io]
    [com.walmartlabs.lacinia.schema :as schema]
    [com.walmartlabs.lacinia.util :as util]))

(defn ^:private literal-factory
  [literal-value]
  (fn [context args value]
    literal-value))

(defn hello-schema
  []
  (-> (io/resource "hello.edn")
    slurp
    edn/read-string
    (util/attach-resolvers {:literal literal-factory})
    schema/compile))

The attach-resolvers function will see the [:literal  "Hello World"] in the schema, and will invoke literal-factory, passing it the "Hello World" argument. literal-factory is responsible for returning the actual field resolver.

A field resolver factory may have any number of arguments.

Common uses for field resolver factories:

  • Mapping GraphQL field names to Clojure hyphenated names
  • Converting or formatting a raw value into a selected value
  • Accessing a deeply nested value in a structure