Skip to content

Commit 4f3388f

Browse files
authored
Add sangria as library implementing GraphQL Federation (#237)
One test is still mising: with @link Fix #232
1 parent 41c1166 commit 4f3388f

24 files changed

+836
-0
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: Sangria Test
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
paths:
8+
- 'implementations/sangria/**'
9+
10+
jobs:
11+
compatibility:
12+
uses: ./.github/workflows/test-subgraph.yaml
13+
with:
14+
library: "sangria"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
version = "3.1.1"
2+
runner.dialect = scala213
3+
4+
maxColumn = 120

implementations/sangria/Dockerfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
FROM hseeberger/scala-sbt:17.0.2_1.6.2_3.1.1 AS build
2+
3+
WORKDIR /build
4+
COPY project/build.properties ./project/
5+
COPY build.sbt ./
6+
COPY src ./src
7+
EXPOSE 4001
8+
CMD sbt run

implementations/sangria/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# federated subgraph with sangria
2+
3+
Implementation of a federated subgraph based on the scala library [sangria](https://sangria-graphql.github.io/).
4+
5+
6+
# Usage
7+
8+
## Starting the GraphQL server
9+
10+
```
11+
sbt run
12+
```
13+
14+
## Printing the GraphQL schema (SQL)
15+
16+
```
17+
sbt "run printSchema"
18+
```

implementations/sangria/build.sbt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import sbt._
2+
3+
version := "1.0.0"
4+
scalaVersion := "2.13.9"
5+
6+
val http4sVersion = "1.0.0-M37"
7+
val circeVersion = "0.14.3"
8+
9+
libraryDependencies ++= List(
10+
"org.sangria-graphql" %% "sangria-federated" % "0.3.0",
11+
"org.sangria-graphql" %% "sangria-circe" % "1.3.2",
12+
"org.http4s" %% "http4s-ember-server" % http4sVersion,
13+
"org.http4s" %% "http4s-dsl" % http4sVersion,
14+
"org.http4s" %% "http4s-circe" % http4sVersion,
15+
"io.circe" %% "circe-core" % circeVersion,
16+
"io.circe" %% "circe-generic" % circeVersion,
17+
"io.circe" %% "circe-optics" % "0.14.1"
18+
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
services:
2+
products:
3+
# must be relative to the root of the project
4+
build: implementations/sangria
5+
ports:
6+
- 4001:4001

implementations/sangria/metadata.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fullName: Sangria
2+
language: Scala
3+
documentation: https://sangria-graphql.github.io/learn/#graphql-federation
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
extend schema
2+
@link(
3+
url: "https://specs.apollo.dev/federation/v2.0",
4+
import: ["@extends", "@external", "@inaccessible", "@key", "@override", "@provides", "@requires", "@shareable", "@tag"]
5+
)
6+
7+
type Product @key(fields: "id") @key(fields: "sku package") @key(fields: "sku variation { id }") {
8+
id: ID!
9+
sku: String
10+
package: String
11+
variation: ProductVariation
12+
dimensions: ProductDimension
13+
createdBy: User @provides(fields: "totalProductsCreated")
14+
notes: String @tag(name: "internal")
15+
research: [ProductResearch!]!
16+
}
17+
18+
type DeprecatedProduct @key(fields: "sku package") {
19+
sku: String!
20+
package: String!
21+
reason: String
22+
createdBy: User
23+
}
24+
25+
type ProductVariation {
26+
id: ID!
27+
}
28+
29+
type ProductResearch @key(fields: "study { caseNumber }") {
30+
study: CaseStudy!
31+
outcome: String
32+
}
33+
34+
type CaseStudy {
35+
caseNumber: ID!
36+
description: String
37+
}
38+
39+
type ProductDimension @shareable {
40+
size: String
41+
weight: Float
42+
unit: String @inaccessible
43+
}
44+
45+
extend type Query {
46+
product(id: ID!): Product
47+
deprecatedProduct(sku: String!, package: String!): DeprecatedProduct @deprecated(reason: "Use product query instead")
48+
}
49+
50+
type User @key(fields: "email") {
51+
averageProductsCreatedPerYear: Int @requires(fields: "totalProductsCreated yearsOfEmployment")
52+
email: ID! @external
53+
name: String @override(from: "users")
54+
totalProductsCreated: Int @external
55+
yearsOfEmployment: Int! @external
56+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
sbt.version=1.6.2
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import cats.effect.{ExitCode, IO, IOApp}
2+
import com.comcast.ip4s.IpLiteralSyntax
3+
import graphql.{AppContext, GraphQLSchema, ProductGraphQLSchema, UserGraphQLSchema}
4+
import http.{GraphQLExecutor, GraphQLServer}
5+
import io.circe.Json
6+
import sangria.federation.v2.Federation
7+
import sangria.marshalling.InputUnmarshaller
8+
import sangria.renderer.QueryRenderer
9+
import sangria.schema.Schema
10+
import service.{ProductResearchService, ProductService, UserService}
11+
12+
object Main extends IOApp {
13+
14+
override def run(args: List[String]): IO[ExitCode] = (args match {
15+
case "printSchema" :: Nil => printSchema
16+
case _ => runGraphQLServer
17+
}).as(ExitCode.Success)
18+
19+
private def printSchema: IO[Unit] =
20+
for {
21+
schema <- IO(schemaAndUm).map(_._1)
22+
_ <- IO.println(QueryRenderer.renderPretty(schema.toAst))
23+
} yield ()
24+
25+
private def runGraphQLServer: IO[Unit] =
26+
for {
27+
ctx <- appContext
28+
executor <- graphQLExecutor(ctx)
29+
host = host"0.0.0.0"
30+
port = port"4001"
31+
_ <- IO.println(s"starting GraphQL HTTP server on http://$host:$port")
32+
_ <- GraphQLServer.bind(executor, host, port).use(_ => IO.never)
33+
} yield ()
34+
35+
private def appContext: IO[AppContext] = IO {
36+
new AppContext {
37+
override def productService: ProductService = ProductService.inMemory
38+
override def productResearchService: ProductResearchService = ProductResearchService.inMemory
39+
override def userService: UserService = UserService.inMemory
40+
}
41+
}
42+
43+
private def schemaAndUm: (Schema[AppContext, Unit], InputUnmarshaller[Json]) =
44+
Federation.federate(
45+
GraphQLSchema.schema,
46+
sangria.marshalling.circe.CirceInputUnmarshaller,
47+
ProductGraphQLSchema.productResolver,
48+
ProductGraphQLSchema.deprecatedProductResolver,
49+
ProductGraphQLSchema.productResearchResolver,
50+
UserGraphQLSchema.userResolver
51+
)
52+
53+
private def graphQLExecutor(context: AppContext): IO[GraphQLExecutor[AppContext]] = IO {
54+
val (schema, um) = schemaAndUm
55+
GraphQLExecutor(schema, context)(um)
56+
}
57+
}

0 commit comments

Comments
 (0)