(defprofile lagénorhynque
:aliases [カマイルカ🐬]
:languages [Clojure Common-Lisp Scheme Haskell
English français]
:interests [programming Love-Live!
language-learning/linguistics law mathematics]
:committing [github.com/lagenorhynque/duct.module.pedestal]
:contributing [github.com/japan-clojurians/clojure-site-ja])
-
GraphQL
-
GraphQL query
-
GraphQL schema
-
-
API implementation
-
project structure
-
API server
-
GraphQL implementation
-
ClojureのLaciniaでGraphQL API開発してみた
lagenorhynque/aqoursql: AqoursQL, an example GraphQL API based on Lacinia-Pedestal & Duct
venia & edn
{
member_by_id(id: 1) {
id
name
organization_id
organization_name
}
}
{
"data": {
"member_by_id": {
"id": 1,
"name": "高海 千歌",
"organization_id": 1,
"organization_name": "浦の星女学院"
}
}
}
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 "浦の星女学院"}}}
(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)})))
{
songs {
name
artist {
name
members {
name
}
}
}
}
{
"data": {
"songs": [
{
"name": "君のこころは輝いてるかい?",
"artist": {
"name": "Aqours",
"members": [
{
"name": "高海 千歌"
}, ...
]
}
}, ...
]
}
}
dev> (q #:venia{:queries [[:songs
[:name
[:artist
[:name
[:members
[:name]]]]]]]})
{:data
{:songs
({:name "君のこころは輝いてるかい?",
:artist
{:name "Aqours",
:members
({:name "高海 千歌"}
...
"""メンバー"""
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 "所属組織名"}}}}}
"""楽曲"""
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)"}}}}}
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}}}
type Query {
"""楽曲一覧取得"""
songs(
"楽曲名 (部分一致)"
name: String
): [Song]
}
{:queries
{:songs
{:type (list :Song)
:description "楽曲一覧取得"
:args
{:name {:type String
:description "楽曲名 (部分一致)"}}
:resolve :query/songs}}}
Duct + Pedestal + Lacinia
{: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 {}}
{: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 {}}
{: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}}}
... }
(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))
(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?)))
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 "浦の星女学院"}}}
- Lacinia
- Lacinia-Pedestal: Expose Lacinia GraphQL as Pedestal endpoints
- ClojureのLaciniaでGraphQL API開発してみた
- example: lagenorhynque/aqoursql
- ClojureでGraphQLサーバを立てる
- Lacinia アプリ開発 Tips
- Duct
- duct.module.pedestal: Duct module for Pedestal
- ClojureサーバサイドフレームワークDuctガイド
- 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