From 38fd7011dacd40b20d37d0d142648fbfc72aa379 Mon Sep 17 00:00:00 2001
From: Dariusz Kuc <9501705+dariuszkuc@users.noreply.github.com>
Date: Tue, 25 Oct 2022 15:15:36 -0500
Subject: [PATCH] Dgraph integration (#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
---
.github/workflows/test-subgraph-dgraph.yaml | 14 +++++
README.md | 3 +-
implementations/dgraph/docker-compose.yaml | 62 ++++++++++++++++++++
implementations/dgraph/metadata.yaml | 3 +
implementations/dgraph/populateData.json | 65 +++++++++++++++++++++
implementations/dgraph/products.graphql | 54 +++++++++++++++++
implementations/dgraph/proxy.conf.template | 8 +++
implementations/dgraph/resolvers.js | 43 ++++++++++++++
src/index.ts | 2 +-
9 files changed, 252 insertions(+), 2 deletions(-)
create mode 100644 .github/workflows/test-subgraph-dgraph.yaml
create mode 100644 implementations/dgraph/docker-compose.yaml
create mode 100644 implementations/dgraph/metadata.yaml
create mode 100644 implementations/dgraph/populateData.json
create mode 100644 implementations/dgraph/products.graphql
create mode 100644 implementations/dgraph/proxy.conf.template
create mode 100644 implementations/dgraph/resolvers.js
diff --git a/.github/workflows/test-subgraph-dgraph.yaml b/.github/workflows/test-subgraph-dgraph.yaml
new file mode 100644
index 000000000..a4ae2d99e
--- /dev/null
+++ b/.github/workflows/test-subgraph-dgraph.yaml
@@ -0,0 +1,14 @@
+name: Dgraph Test
+
+on:
+ pull_request:
+ branches:
+ - main
+ paths:
+ - 'implementations/dgraph/**'
+
+jobs:
+ compatibility:
+ uses: ./.github/workflows/test-subgraph.yaml
+ with:
+ library: "dgraph"
\ No newline at end of file
diff --git a/README.md b/README.md
index 89c395271..bd581d5bd 100644
--- a/README.md
+++ b/README.md
@@ -159,8 +159,9 @@ The following open-source GraphQL server libraries and hosted subgraphs provide
AWS AppSync | _service | 🟢 |
---|
@key (single) | 🟢 |
---|
@key (multi) | 🟢 |
---|
@key (composite) | 🟢 |
---|
repeatable @key | 🟢 |
---|
@requires | 🟢 |
---|
@provides | 🟢 |
---|
federated tracing | 🔲 |
---|
| @link | 🟢 |
---|
@shareable | 🟢 |
---|
@tag | 🟢 |
---|
@override | 🟢 |
---|
@inaccessible | 🟢 |
---|
|
-StepZen | _service | 🟢 |
---|
@key (single) | 🟢 |
---|
@key (multi) | 🟢 |
---|
@key (composite) | 🔲 |
---|
repeatable @key | 🔲 |
---|
@requires | 🟢 |
---|
@provides | 🟢 |
---|
federated tracing | 🔲 |
---|
| @link | 🟢 |
---|
@shareable | 🟢 |
---|
@tag | 🟢 |
---|
@override | 🟢 |
---|
@inaccessible | 🟢 |
---|
|
+Dgraph | _service | ❌ |
---|
@key (single) | 🟢 |
---|
@key (multi) | 🔲 |
---|
@key (composite) | 🔲 |
---|
repeatable @key | 🔲 |
---|
@requires | 🔲 |
---|
@provides | 🔲 |
---|
federated tracing | 🔲 |
---|
| @link | ❌ |
---|
@shareable | 🔲 |
---|
@tag | 🔲 |
---|
@override | 🔲 |
---|
@inaccessible | 🔲 |
---|
|
GraphQL Mesh | _service | 🟢 |
---|
@key (single) | 🟢 |
---|
@key (multi) | 🟢 |
---|
@key (composite) | 🟢 |
---|
repeatable @key | 🟢 |
---|
@requires | 🟢 |
---|
@provides | 🟢 |
---|
federated tracing | 🟢 |
---|
| @link | 🟢 |
---|
@shareable | 🟢 |
---|
@tag | 🟢 |
---|
@override | 🟢 |
---|
@inaccessible | 🟢 |
---|
|
+StepZen | _service | 🟢 |
---|
@key (single) | 🟢 |
---|
@key (multi) | 🟢 |
---|
@key (composite) | 🔲 |
---|
repeatable @key | 🔲 |
---|
@requires | 🟢 |
---|
@provides | 🟢 |
---|
federated tracing | 🔲 |
---|
| @link | 🟢 |
---|
@shareable | 🟢 |
---|
@tag | 🟢 |
---|
@override | 🟢 |
---|
@inaccessible | 🟢 |
---|
|
diff --git a/implementations/dgraph/docker-compose.yaml b/implementations/dgraph/docker-compose.yaml
new file mode 100644
index 000000000..bd2734f31
--- /dev/null
+++ b/implementations/dgraph/docker-compose.yaml
@@ -0,0 +1,62 @@
+# all path must be relative to the root of the project
+services:
+ dgraph:
+ image: dgraph/standalone:v21.03.2
+ environment:
+ DGRAPH_ALPHA_GRAPHQL: 'lambda-url=http://dgraph_lambda:8686/graphql-worker'
+ ports:
+ - "8080:8080"
+ - "9080:9080"
+ - "8000:8000"
+ volumes:
+ - dgraph:/dgraph
+ healthcheck:
+ test: ["CMD", "curl", "-X", "GET", "http://dgraph:8080/admin?query=\\{health\\{status\\}\\}"]
+ interval: 15s
+ timeout: 10s
+ retries: 20
+
+ dgraph_lambda:
+ image: dgraph/dgraph-lambda:1.4.0
+ depends_on:
+ - dgraph
+ ports:
+ - "8686:8686"
+ environment:
+ DGRAPH_URL: http://dgraph:8080
+ MAX_MEMORY_LIMIT: 192M
+ volumes:
+ - ./implementations/dgraph/resolvers.js:/app/script/script.js:ro
+
+ dgraph_data:
+ image: curlimages/curl:7.85.0
+ depends_on:
+ dgraph:
+ condition: service_healthy
+ restart: "no"
+ working_dir: /app
+ volumes:
+ - ./implementations/dgraph/products.graphql:/app/products.graphql:ro
+ - ./implementations/dgraph/populateData.json:/app/populateData.json:ro
+ command:
+ - /bin/sh
+ - -c
+ - |
+ curl -v -X POST http://dgraph:8080/admin/schema --data-binary '@products.graphql'
+ curl -v -X POST -H 'Content-Type:application/json' http://dgraph:8080/graphql --data-binary '@populateData.json'
+
+ products:
+ image: nginx:alpine
+ ports:
+ - 4001:4001
+ depends_on:
+ dgraph:
+ condition: service_healthy
+ environment:
+ DGRAPH_URL: http://dgraph:8080/graphql
+ volumes:
+ - ./implementations/dgraph/proxy.conf.template:/etc/nginx/conf.d/proxy.conf.template
+ command: /bin/sh -c "envsubst < /etc/nginx/conf.d/proxy.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"
+
+volumes:
+ dgraph: {}
\ No newline at end of file
diff --git a/implementations/dgraph/metadata.yaml b/implementations/dgraph/metadata.yaml
new file mode 100644
index 000000000..3e7fa4af4
--- /dev/null
+++ b/implementations/dgraph/metadata.yaml
@@ -0,0 +1,3 @@
+fullName: Dgraph
+language: Other Solutions
+documentation: https://dgraph.io/docs/graphql/overview/
diff --git a/implementations/dgraph/populateData.json b/implementations/dgraph/populateData.json
new file mode 100644
index 000000000..639069747
--- /dev/null
+++ b/implementations/dgraph/populateData.json
@@ -0,0 +1,65 @@
+{
+ "query": "mutation populateData($products: [AddProductInput!]!, $deprecatedProd: [AddDeprecatedProductInput!]!) { addProduct(input: $products) { numUids }, addDeprecatedProduct(input: $deprecatedProd) { numUids } }",
+ "variables": {
+ "products": [{
+ "id": "apollo-federation",
+ "sku": "federation",
+ "package": "@apollo/federation",
+ "variation": {
+ "id": "OSS"
+ },
+ "dimensions": {
+ "size": "small",
+ "weight": 1,
+ "unit": "kg"
+ },
+ "research": [
+ {
+ "study": {
+ "caseNumber": "1234",
+ "description": "Federation Study"
+ }
+ }
+ ],
+ "createdBy": {
+ "email": "support@apollographql.com",
+ "name": "Jane Smith",
+ "totalProductsCreated": 1337
+ }
+ },{
+ "id": "apollo-studio",
+ "sku": "studi",
+ "variation": {
+ "id": "platform"
+ },
+ "dimensions": {
+ "size": "small",
+ "weight": 1,
+ "unit": "kg"
+ },
+ "research": [
+ {
+ "study": {
+ "caseNumber": "1235",
+ "description": "Studio Study"
+ }
+ }
+ ],
+ "createdBy": {
+ "email": "support@apollographql.com",
+ "name": "Jane Smith",
+ "totalProductsCreated": 1337
+ }
+ }],
+ "deprecatedProd": [{
+ "sku": "apollo-federation-v1",
+ "package": "@apollo/federation-v1",
+ "reason": "Migrate to Federation V2",
+ "createdBy": {
+ "email": "support@apollographql.com",
+ "name": "Jane Smith",
+ "totalProductsCreated": 1337
+ }
+ }]
+ }
+}
diff --git a/implementations/dgraph/products.graphql b/implementations/dgraph/products.graphql
new file mode 100644
index 000000000..a08deee66
--- /dev/null
+++ b/implementations/dgraph/products.graphql
@@ -0,0 +1,54 @@
+type Product @key(fields: "id") @generate(query: { get: true, query: false, aggregate: false }) {
+ # dgraph does not support custom values for ID fields
+ id: String! @id
+ sku: String
+ package: String
+ variation: ProductVariation
+ dimensions: ProductDimension
+ createdBy: User @provides(fields: "totalProductsCreated")
+ notes: String
+ research: [ProductResearch!]!
+}
+
+type DeprecatedProduct @generate(query: { get: true, query: false, aggregate: false }) {
+ sku: String! @id
+ package: String! @id
+ reason: String
+ createdBy: User
+}
+
+type ProductVariation @generate(query: { get: false, query: false, aggregate: false }) {
+ id: String! @id
+ # dgraph does not allow creating types with just ID value
+ dummy: String
+}
+
+type ProductResearch @generate(query: { get: false, query: false, aggregate: false }) {
+ study: CaseStudy!
+ outcome: String
+}
+
+type CaseStudy @generate(query: { get: false, query: false, aggregate: false }) {
+ caseNumber: String! @id
+ description: String
+}
+
+type ProductDimension @generate(query: { get: false, query: false, aggregate: false }) {
+ size: String
+ weight: Float
+ unit: String
+}
+
+type Query {
+ product(id: String!): Product @lambda
+ deprecatedProduct(sku: String!, package: String!): DeprecatedProduct @deprecated(reason: "Use product query instead") @lambda
+}
+
+extend type User @key(fields: "email") @generate(query: { get: false, query: false, aggregate: false }) {
+ averageProductsCreatedPerYear: Int @requires(fields: "totalProductsCreated yearsOfEmployment") @lambda
+ email: String! @id
+ name: String
+ totalProductsCreated: Int @external
+ # dgraph does not support non-nullable external types
+ yearsOfEmployment: Int @external
+}
diff --git a/implementations/dgraph/proxy.conf.template b/implementations/dgraph/proxy.conf.template
new file mode 100644
index 000000000..97cdc5625
--- /dev/null
+++ b/implementations/dgraph/proxy.conf.template
@@ -0,0 +1,8 @@
+server {
+ listen 4001;
+ server_name products;
+ location / {
+ proxy_ssl_server_name on;
+ proxy_pass ${DGRAPH_URL};
+ }
+}
\ No newline at end of file
diff --git a/implementations/dgraph/resolvers.js b/implementations/dgraph/resolvers.js
new file mode 100644
index 000000000..46bfe5172
--- /dev/null
+++ b/implementations/dgraph/resolvers.js
@@ -0,0 +1,43 @@
+async function productById({ args, graphql }) {
+ const results = await graphql(`query GetProductById($productId: String!) {
+ getProduct(id: $productId) {
+ id
+ sku
+ package
+ variation { id }
+ dimensions { size weight unit }
+ createdBy { email name totalProductsCreated }
+ research { study { caseNumber description }}
+ }
+ }`, {"productId": args.id})
+ return results.data.getProduct
+ }
+
+async function deprecatedProductBySkuAndPackage({ args, graphql }) {
+ console.log("executing deprecated product by sku and package", args)
+ const results = await graphql(`query GetDeprecatedProductBySkuAndPackage($sku: String!, $pkg: String!) {
+ getDeprecatedProduct(sku: $sku, package: $pkg) {
+ sku
+ package
+ reason
+ createdBy { email name totalProductsCreated }
+ }
+ }`, {"sku": args.sku, "pkg": args.package})
+ console.log("executed query")
+ console.log("results", results)
+ return results.data.getDeprecatedProduct
+}
+
+async function userAverageProductsCreatedPerYear({parent: {totalProductsCreated, yearsOfEmployment}}) {
+ if (totalProductsCreated) {
+ Math.round(totalProductsCreated / yearsOfEmployment)
+ } else {
+ null
+ }
+}
+
+self.addGraphQLResolvers({
+ "Query.product": productById,
+ "Query.deprecatedProduct": deprecatedProductBySkuAndPackage,
+ "User.averageProductsCreatedPerYear": userAverageProductsCreatedPerYear
+})
diff --git a/src/index.ts b/src/index.ts
index 2418d14c7..4ecc9f4ba 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -54,7 +54,7 @@ async function runDockerCompose(libraryName: string, librariesPath: string) {
return async () => {
console.log(`Stopping ${libraryName} and related containers...`);
- await execa("docker-compose", ["down", "--remove-orphans"]);
+ await execa("docker-compose", ["down", "--remove-orphans", "-v"]);
};
}