Skip to content

Commit 38fd701

Browse files
authored
Dgraph integration (apollographql#253)
Dgraph currently has very limited federation support. Due to the limitations in schema declaration it is not possible to implement the expected schema: * `ID` fields are autogenerated and cannot have custom values (in order to set custom ID we need to use `String @id`) * unable to create type with just `ID` column (as it doesn't make sense to have a table with a single ID column) * unable to disable auto generated field aggregate query (`Product.reserach`) * only single field `@key` values are supported, multi column or complex type keys are not supported * external fields have to be nullable
1 parent f8bfd56 commit 38fd701

File tree

9 files changed

+252
-2
lines changed

9 files changed

+252
-2
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: Dgraph Test
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
paths:
8+
- 'implementations/dgraph/**'
9+
10+
jobs:
11+
compatibility:
12+
uses: ./.github/workflows/test-subgraph.yaml
13+
with:
14+
library: "dgraph"

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,9 @@ The following open-source GraphQL server libraries and hosted subgraphs provide
159159
</thead>
160160
<tbody>
161161
<tr><td><a href="https://aws.amazon.com/appsync/">AWS AppSync</a></td><td><table><tr><th>_service</th><td>🟢</td></tr><tr><th>@key (single)</th><td>🟢</td></tr><tr><th>@key (multi)</th><td>🟢</td></tr><tr><th>@key (composite)</th><td>🟢</td></tr><tr><th>repeatable @key</th><td>🟢</td></tr><tr><th>@requires</th><td>🟢</td></tr><tr><th>@provides</th><td>🟢</td></tr><tr><th>federated tracing</th><td>🔲</td></tr></table></td><td><table><tr><th>@link</th><td>🟢</td></tr><tr><th>@shareable</th><td>🟢</td></tr><tr><th>@tag</th><td>🟢</td></tr><tr><th>@override</th><td>🟢</td></tr><tr><th>@inaccessible</th><td>🟢</td></tr></table></td></tr>
162-
<tr><td><a href="https://stepzen.com/apollo-stepzen">StepZen</a></td><td><table><tr><th>_service</th><td>🟢</td></tr><tr><th>@key (single)</th><td>🟢</td></tr><tr><th>@key (multi)</th><td>🟢</td></tr><tr><th>@key (composite)</th><td>🔲</td></tr><tr><th>repeatable @key</th><td>🔲</td></tr><tr><th>@requires</th><td>🟢</td></tr><tr><th>@provides</th><td>🟢</td></tr><tr><th>federated tracing</th><td>🔲</td></tr></table></td><td><table><tr><th>@link</th><td>🟢</td></tr><tr><th>@shareable</th><td>🟢</td></tr><tr><th>@tag</th><td>🟢</td></tr><tr><th>@override</th><td>🟢</td></tr><tr><th>@inaccessible</th><td>🟢</td></tr></table></td></tr>
162+
<tr><td><a href="https://dgraph.io/docs/graphql/overview/">Dgraph</a></td><td><table><tr><th>_service</th><td></td></tr><tr><th>@key (single)</th><td>🟢</td></tr><tr><th>@key (multi)</th><td>🔲</td></tr><tr><th>@key (composite)</th><td>🔲</td></tr><tr><th>repeatable @key</th><td>🔲</td></tr><tr><th>@requires</th><td>🔲</td></tr><tr><th>@provides</th><td>🔲</td></tr><tr><th>federated tracing</th><td>🔲</td></tr></table></td><td><table><tr><th>@link</th><td></td></tr><tr><th>@shareable</th><td>🔲</td></tr><tr><th>@tag</th><td>🔲</td></tr><tr><th>@override</th><td>🔲</td></tr><tr><th>@inaccessible</th><td>🔲</td></tr></table></td></tr>
163163
<tr><td><a href="https://www.the-guild.dev/graphql/mesh">GraphQL Mesh</a></td><td><table><tr><th>_service</th><td>🟢</td></tr><tr><th>@key (single)</th><td>🟢</td></tr><tr><th>@key (multi)</th><td>🟢</td></tr><tr><th>@key (composite)</th><td>🟢</td></tr><tr><th>repeatable @key</th><td>🟢</td></tr><tr><th>@requires</th><td>🟢</td></tr><tr><th>@provides</th><td>🟢</td></tr><tr><th>federated tracing</th><td>🟢</td></tr></table></td><td><table><tr><th>@link</th><td>🟢</td></tr><tr><th>@shareable</th><td>🟢</td></tr><tr><th>@tag</th><td>🟢</td></tr><tr><th>@override</th><td>🟢</td></tr><tr><th>@inaccessible</th><td>🟢</td></tr></table></td></tr>
164+
<tr><td><a href="https://stepzen.com/apollo-stepzen">StepZen</a></td><td><table><tr><th>_service</th><td>🟢</td></tr><tr><th>@key (single)</th><td>🟢</td></tr><tr><th>@key (multi)</th><td>🟢</td></tr><tr><th>@key (composite)</th><td>🔲</td></tr><tr><th>repeatable @key</th><td>🔲</td></tr><tr><th>@requires</th><td>🟢</td></tr><tr><th>@provides</th><td>🟢</td></tr><tr><th>federated tracing</th><td>🔲</td></tr></table></td><td><table><tr><th>@link</th><td>🟢</td></tr><tr><th>@shareable</th><td>🟢</td></tr><tr><th>@tag</th><td>🟢</td></tr><tr><th>@override</th><td>🟢</td></tr><tr><th>@inaccessible</th><td>🟢</td></tr></table></td></tr>
164165
</tbody>
165166
</table>
166167

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# all path must be relative to the root of the project
2+
services:
3+
dgraph:
4+
image: dgraph/standalone:v21.03.2
5+
environment:
6+
DGRAPH_ALPHA_GRAPHQL: 'lambda-url=http://dgraph_lambda:8686/graphql-worker'
7+
ports:
8+
- "8080:8080"
9+
- "9080:9080"
10+
- "8000:8000"
11+
volumes:
12+
- dgraph:/dgraph
13+
healthcheck:
14+
test: ["CMD", "curl", "-X", "GET", "http://dgraph:8080/admin?query=\\{health\\{status\\}\\}"]
15+
interval: 15s
16+
timeout: 10s
17+
retries: 20
18+
19+
dgraph_lambda:
20+
image: dgraph/dgraph-lambda:1.4.0
21+
depends_on:
22+
- dgraph
23+
ports:
24+
- "8686:8686"
25+
environment:
26+
DGRAPH_URL: http://dgraph:8080
27+
MAX_MEMORY_LIMIT: 192M
28+
volumes:
29+
- ./implementations/dgraph/resolvers.js:/app/script/script.js:ro
30+
31+
dgraph_data:
32+
image: curlimages/curl:7.85.0
33+
depends_on:
34+
dgraph:
35+
condition: service_healthy
36+
restart: "no"
37+
working_dir: /app
38+
volumes:
39+
- ./implementations/dgraph/products.graphql:/app/products.graphql:ro
40+
- ./implementations/dgraph/populateData.json:/app/populateData.json:ro
41+
command:
42+
- /bin/sh
43+
- -c
44+
- |
45+
curl -v -X POST http://dgraph:8080/admin/schema --data-binary '@products.graphql'
46+
curl -v -X POST -H 'Content-Type:application/json' http://dgraph:8080/graphql --data-binary '@populateData.json'
47+
48+
products:
49+
image: nginx:alpine
50+
ports:
51+
- 4001:4001
52+
depends_on:
53+
dgraph:
54+
condition: service_healthy
55+
environment:
56+
DGRAPH_URL: http://dgraph:8080/graphql
57+
volumes:
58+
- ./implementations/dgraph/proxy.conf.template:/etc/nginx/conf.d/proxy.conf.template
59+
command: /bin/sh -c "envsubst < /etc/nginx/conf.d/proxy.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"
60+
61+
volumes:
62+
dgraph: {}

implementations/dgraph/metadata.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fullName: Dgraph
2+
language: Other Solutions
3+
documentation: https://dgraph.io/docs/graphql/overview/
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"query": "mutation populateData($products: [AddProductInput!]!, $deprecatedProd: [AddDeprecatedProductInput!]!) { addProduct(input: $products) { numUids }, addDeprecatedProduct(input: $deprecatedProd) { numUids } }",
3+
"variables": {
4+
"products": [{
5+
"id": "apollo-federation",
6+
"sku": "federation",
7+
"package": "@apollo/federation",
8+
"variation": {
9+
"id": "OSS"
10+
},
11+
"dimensions": {
12+
"size": "small",
13+
"weight": 1,
14+
"unit": "kg"
15+
},
16+
"research": [
17+
{
18+
"study": {
19+
"caseNumber": "1234",
20+
"description": "Federation Study"
21+
}
22+
}
23+
],
24+
"createdBy": {
25+
"email": "[email protected]",
26+
"name": "Jane Smith",
27+
"totalProductsCreated": 1337
28+
}
29+
},{
30+
"id": "apollo-studio",
31+
"sku": "studi",
32+
"variation": {
33+
"id": "platform"
34+
},
35+
"dimensions": {
36+
"size": "small",
37+
"weight": 1,
38+
"unit": "kg"
39+
},
40+
"research": [
41+
{
42+
"study": {
43+
"caseNumber": "1235",
44+
"description": "Studio Study"
45+
}
46+
}
47+
],
48+
"createdBy": {
49+
"email": "[email protected]",
50+
"name": "Jane Smith",
51+
"totalProductsCreated": 1337
52+
}
53+
}],
54+
"deprecatedProd": [{
55+
"sku": "apollo-federation-v1",
56+
"package": "@apollo/federation-v1",
57+
"reason": "Migrate to Federation V2",
58+
"createdBy": {
59+
"email": "[email protected]",
60+
"name": "Jane Smith",
61+
"totalProductsCreated": 1337
62+
}
63+
}]
64+
}
65+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
type Product @key(fields: "id") @generate(query: { get: true, query: false, aggregate: false }) {
2+
# dgraph does not support custom values for ID fields
3+
id: String! @id
4+
sku: String
5+
package: String
6+
variation: ProductVariation
7+
dimensions: ProductDimension
8+
createdBy: User @provides(fields: "totalProductsCreated")
9+
notes: String
10+
research: [ProductResearch!]!
11+
}
12+
13+
type DeprecatedProduct @generate(query: { get: true, query: false, aggregate: false }) {
14+
sku: String! @id
15+
package: String! @id
16+
reason: String
17+
createdBy: User
18+
}
19+
20+
type ProductVariation @generate(query: { get: false, query: false, aggregate: false }) {
21+
id: String! @id
22+
# dgraph does not allow creating types with just ID value
23+
dummy: String
24+
}
25+
26+
type ProductResearch @generate(query: { get: false, query: false, aggregate: false }) {
27+
study: CaseStudy!
28+
outcome: String
29+
}
30+
31+
type CaseStudy @generate(query: { get: false, query: false, aggregate: false }) {
32+
caseNumber: String! @id
33+
description: String
34+
}
35+
36+
type ProductDimension @generate(query: { get: false, query: false, aggregate: false }) {
37+
size: String
38+
weight: Float
39+
unit: String
40+
}
41+
42+
type Query {
43+
product(id: String!): Product @lambda
44+
deprecatedProduct(sku: String!, package: String!): DeprecatedProduct @deprecated(reason: "Use product query instead") @lambda
45+
}
46+
47+
extend type User @key(fields: "email") @generate(query: { get: false, query: false, aggregate: false }) {
48+
averageProductsCreatedPerYear: Int @requires(fields: "totalProductsCreated yearsOfEmployment") @lambda
49+
email: String! @id
50+
name: String
51+
totalProductsCreated: Int @external
52+
# dgraph does not support non-nullable external types
53+
yearsOfEmployment: Int @external
54+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
server {
2+
listen 4001;
3+
server_name products;
4+
location / {
5+
proxy_ssl_server_name on;
6+
proxy_pass ${DGRAPH_URL};
7+
}
8+
}

implementations/dgraph/resolvers.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
async function productById({ args, graphql }) {
2+
const results = await graphql(`query GetProductById($productId: String!) {
3+
getProduct(id: $productId) {
4+
id
5+
sku
6+
package
7+
variation { id }
8+
dimensions { size weight unit }
9+
createdBy { email name totalProductsCreated }
10+
research { study { caseNumber description }}
11+
}
12+
}`, {"productId": args.id})
13+
return results.data.getProduct
14+
}
15+
16+
async function deprecatedProductBySkuAndPackage({ args, graphql }) {
17+
console.log("executing deprecated product by sku and package", args)
18+
const results = await graphql(`query GetDeprecatedProductBySkuAndPackage($sku: String!, $pkg: String!) {
19+
getDeprecatedProduct(sku: $sku, package: $pkg) {
20+
sku
21+
package
22+
reason
23+
createdBy { email name totalProductsCreated }
24+
}
25+
}`, {"sku": args.sku, "pkg": args.package})
26+
console.log("executed query")
27+
console.log("results", results)
28+
return results.data.getDeprecatedProduct
29+
}
30+
31+
async function userAverageProductsCreatedPerYear({parent: {totalProductsCreated, yearsOfEmployment}}) {
32+
if (totalProductsCreated) {
33+
Math.round(totalProductsCreated / yearsOfEmployment)
34+
} else {
35+
null
36+
}
37+
}
38+
39+
self.addGraphQLResolvers({
40+
"Query.product": productById,
41+
"Query.deprecatedProduct": deprecatedProductBySkuAndPackage,
42+
"User.averageProductsCreatedPerYear": userAverageProductsCreatedPerYear
43+
})

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ async function runDockerCompose(libraryName: string, librariesPath: string) {
5454

5555
return async () => {
5656
console.log(`Stopping ${libraryName} and related containers...`);
57-
await execa("docker-compose", ["down", "--remove-orphans"]);
57+
await execa("docker-compose", ["down", "--remove-orphans", "-v"]);
5858
};
5959
}
6060

0 commit comments

Comments
 (0)