Service Implementation

At this point, we’ve discussed what goes into each implementing service’s schema, and a bit about how each service is responsible for resolving representations; let’s finally see how this all fits together with Lacinia.

Below is a sketch of how this comes together in the products service:

(ns products.server
  (:require
   [io.pedestal.http :as http]
   [clojure.java.io :as io]
   [com.walmartlabs.lacinia.pedestal2 :as lp]
   [com.walmartlabs.lacinia.parser.schema :refer [parse-schema]]
   [com.walmartlabs.lacinia.schema :as schema]
   [com.walmartlabs.lacinia.util :as util]))

(defn resolve-users-external
  [_ _ reps]
  (for [{:keys [id]} reps]
    (schema/tag-with-type {:id id} 
                          :User)))

(defn get-product-by-upc
  [context upc]
  ;; Peform DB query here, return map with :upc, :name, :price
  )

(defn get-favorite-products-for-user
  [context user-id]
  ;; Perform DB query here, return seq of maps with :upc, :name, :price
  )

(defn resolve-products-internal
  [context _ reps]
  (for [{:keys [upc]} reps
        :let [product (get-product-by-upc context upc)]]
    (schema/tag-with-type product :Product)))

(defn resolve-product-by-upc
  [context {:keys [upc]} _]
  (get-product-by-upc context upc))

(defn resolve-favorite-products
  [context _ user]
  (let [{:keys [id]} user]
    (get-favorite-products-for-user context id)))

(defn products-schema
  []
  (-> "products.gql"
      io/resource
      slurp
      (parse-schema {:federation {:entity-resolvers {:Product resolve-products-internal
                                                     :User resolve-users-external}}})
      (util/inject-resolvers {:Query/productByUpc #'resolve-product-by-upc
                              :User/favoriteProducts #'resolve-favorite-products})
      schema/compile))

(defn start
  []
  (-> (products-schema)
      lp/default-service
      http/create-server
      http/start))
 

The resolve-users-external function is used to convert a seq of User representations into a seq of User entity stubs; this is called from the resolver for the _entities query whose type is a list of the _Entities union, therefore each value must be tagged with the :User type.

resolve-products-internal does the same for Product representations, but since this is the products service, the expected behavior is to perform a query against an external data store and ensure the results match the structure of the Product entity.

resolve-product-by-upc is the resolver function for the productByUpc query. Since the field type is Product there’s no need to tag the value.

resolve-favorite-products is the resolver function for the User/favoriteProducts field. This is passed the User (provided by resolve-users-external); it extracts the id and passes it to get-favorite-products-for-user.

The remainder is bare-bones scaffolding to read, parse, and compile the schema and build a Pedestal service endpoint around it.

Pay careful attention to the call to com.walmartlabs.lacinia.parser-schema/parse-schema; the presence of the :federation option is critical; this adds the necessary base types and directives before parsing the schema definition, and then adds the _entities query and _Entities union afterwards, among other things.

The :entity-resolvers map is critical; this maps from a type name to a entity resolver; this information is used to build the field resolver function for the _entities query.

Warning

A lot of details are left out of this, such as initializing the database and storing the database connection into the application context, where functions like get-product-by-upc can access it.

This is only a sketch to help you connect the dots.