229 Views
January 31, 19
スライド概要
Introduction to GraphQL API development in Clojure
「楽しく楽にcoolにsmartに」を理想とするprogrammer/philosopher/liberalist/realist。 好きな言語はClojure, Haskell, Python, English, français, русский。 読書、プログラミング、語学、法学、数学が大好き! イルカと海も大好き🐬
GraphQL API in Clojure
lagénorhynque (defprofile lagénorhynque :aliases [ :languages :interests ] [Clojure Common-Lisp Scheme Haskell English français] [programming Love-Live! language-learning/linguistics law mathematics] :committing [github.com/lagenorhynque/duct.module.pedestal] :contributing [github.com/japan-clojurians/clojure-site-ja])
1. GraphQL GraphQL query GraphQL schema 2. API implementation project structure API server GraphQL implementation
Clojure Lacinia GraphQL API
example lagenorhynque/aqoursql: AqoursQL, an example GraphQL API based on Lacinia-Pedestal & Duct
GraphQL venia & edn
GraphQL query venia
query: member_by_id { member_by_id(id: 1) { id name organization_id organization_name } }
response (JSON) { "data": { "member_by_id": { "id": 1, "name": " ", "organization_id": 1, "organization_name": " } } } "
GraphiQL
Clojure REPL dev> (q #:venia{:queries [[:member_by_id {:id 1} [:id :name :organization_id :organization_name]]]}) {:data {:member_by_id {:id 1, :name " ", :organization_id 1, :organization_name " "}}}
utility function q (defn q ([query] (q query nil)) ([query variables] (lacinia/execute (:aqoursql.graphql/schema system) (venia/graphql-query query) variables {:db (:duct.database.sql/hikaricp system)}))) venia.core/graphqlquery com.walmartlabs.lacinia/execute
query: songs { songs { name artist { name members { name } } } }
response (JSON) { "data": { "songs": [ { "name": " "artist": { "name": "Aqours", "members": [ { "name": " }, ... ] } }, ... ] } ", "
GraphiQL
Clojure REPL
dev> (q #:venia{:queries [[:songs
[:name
[:artist
[:name
[:members
[:name]]]]]]]})
{:data
{:songs
({:name "
",
:artist
{:name "Aqours",
:members
({:name "
"}
...
GraphQL schema edn
object de nition: Member """ """ type Member { """ ID""" id: Int! """ """ name: String! """ ID""" organization_id: Int! """ """ organization_name: String! }
resources/aqoursql/graphql-schema.edn {:objects {:Member {:description " " :fields {:id {:type (non-null Int) :description " ID"} :name {:type (non-null String) :description " "} :organization_id {:type (non-null Int) :description " ID"} :organization_name {:type (non-null String) :description " "}}}}}
object de nition: Song """ """ type Song { """ ID""" id: Int! """ """ name: String! """" ID"" artist_id: Int! """ """ artist: Artist! """ (YYYY-MM-DD)""" release_date: String! }
resources/aqoursql/graphql-schema.edn {:objects {:Song {:description " " :fields {:id {:type (non-null Int) :description " ID"} :name {:type (non-null String) :description " "} :artist_id {:type (non-null Int) :description " ID"} :artist {:type (non-null :Artist) :description " "} :release_date {:type (non-null String) :description " (YYYY-MM-DD)"}}}}}
query de nition: member_by_id type Query { """ID member_by_id( " ID" id: Int! ): Member } """
resources/aqoursql/graphql-schema.edn {:queries {:member_by_id {:type :Member :description "ID " :args {:id {:type (non-null Int) :description " ID"}} :resolve :query/member-by-id}}}
query de nition: songs type Query { """ """ songs( " ( )" name: String ): [Song] }
{:queries {:songs {:type (list :Song) :description " " :args {:name {:type String :description " :resolve :query/songs}}} ( )"}}
API implementation Duct + Pedestal + Lacinia
project structure Duct
resources/aqoursql/con g.edn {:duct.profile/base {:duct.core/project-ns aqoursql :duct.server/pedestal { ... } :aqoursql.graphql/schema {} :aqoursql.graphql/service { ... }} :duct.profile/dev #duct/include "dev" :duct.profile/test #duct/include "test" :duct.profile/local #duct/include "local" :duct.profile/prod {} :duct.module/logging {} :duct.module/sql { ... } :duct.module/pedestal {}}
API server Pedestal duct.module.pedestal
Clojure Pedestal
resources/aqoursql/con g.edn {:duct.profile/base {:duct.core/project-ns aqoursql :duct.server/pedestal {:base-service #ig/ref :aqoursql.graphql/service :service #:io.pedestal.http{:join? true :host #duct/env "SERVER_HOST" :port #duct/env ["SERVER_PORT" Int :or 8888]}} ... } ... :duct.module/pedestal {}}
GraphQL implementation Lacinia Lacinia-Pedestal
resources/aqoursql/con g.edn {:duct.profile/base {:duct.core/project-ns aqoursql ... :aqoursql.graphql/schema {} :aqoursql.graphql/service {:schema #ig/ref :aqoursql.graphql/schema :options {:graphiql true :app-context {:db #ig/ref :duct.database/sql} :env :prod}}} ... }
src/aqoursql/graphql.clj
(defmethod ig/init-key ::schema
[_ _]
(-> (io/resource "aqoursql/graphql-schema.edn")
slurp
edn/read-string
(util/attach-resolvers resolver-map)
schema/compile))
(defmethod ig/init-key ::service
[_ {:keys [schema options]}]
(lacinia/service-map schema options))
resolver (function) src/aqoursql/graphql.clj (def resolver-map {:query/artist-by-id artists/fetch-artist-by-id :query/artists artists/list-artists :query/member-by-id members/fetch-member-by-id :query/members members/list-members :query/song-by-id songs/fetch-song-by-id :query/songs songs/list-songs})
src/aqoursql/resolver/members.clj (defn fetch-member-by-id [{:keys [db]} {:keys [id]} _] (db.member/find-member-by-id db id)) resolver function spec (s/fdef resolver :args (s/cat :app-context map? :arguments (s/nilable map?) :resovled-value (s/nilable map?)))
boundary (DB) src/aqoursql/boundary/db/member.clj (s/def ::id nat-int?) (s/def ::name string?) (s/def ::organization_id ::organization/id) (s/def ::organization_name ::organization/name) (s/def ::artist_id ::artist/id) (s/def ::artist_ids (s/coll-of ::artist/id)) (s/def ::member (s/keys :req-un [::id ::name ::organization_id] :opt-un [::organization_name ::artist_id]))
(s/fdef find-member-by-id :args (s/cat :db ::db/db :id ::id) :ret (s/nilable ::member)) ... (defprotocol Member (find-member-by-id [db id]) ... )
dev> (aqoursql.boundary.db.member/find-member-by-id
(:duct.database.sql/hikaricp system) 1)
{:id 1, :name "
", :organization_id 1,
:organization_name "
"}
dev> (aqoursql.resolver.members/fetch-member-by-id
{:db (:duct.database.sql/hikaricp system)}
{:id 1} nil)
{:id 1, :name "
", :organization_id 1,
:organization_name "
"}
dev> (q #:venia{:queries [[:member_by_id {:id 1}
[:name
:organization_name]]]})
{:data {:member_by_id {:name "
",
:organization_name "
"}}}
Further Reading Lacinia Lacinia Lacinia-Pedestal: Expose Lacinia GraphQL as Pedestal endpoints Clojure Lacinia GraphQL API example: lagenorhynque/aqoursql Clojure GraphQL Lacinia Tips
Duct Duct duct.module.pedestal: Duct module for Pedestal Clojure Duct
Pedestal Pedestal Clojure Pedestal
GraphQL venia: Clojure(Script) graphql query generation GraphQL | A query language for your API How to GraphQL - The Fullstack Tutorial for GraphQL GraphQL ─ REST API Learning GraphQL