Skip to content

Commit

Permalink
Dgraph integration (#253)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
dariuszkuc authored Oct 25, 2022
1 parent f8bfd56 commit 38fd701
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 2 deletions.
14 changes: 14 additions & 0 deletions .github/workflows/test-subgraph-dgraph.yaml
Original file line number Diff line number Diff line change
@@ -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"
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,9 @@ The following open-source GraphQL server libraries and hosted subgraphs provide
</thead>
<tbody>
<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>
<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>
<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>
<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>
<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>
</tbody>
</table>

Expand Down
62 changes: 62 additions & 0 deletions implementations/dgraph/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -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: {}
3 changes: 3 additions & 0 deletions implementations/dgraph/metadata.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fullName: Dgraph
language: Other Solutions
documentation: https://dgraph.io/docs/graphql/overview/
65 changes: 65 additions & 0 deletions implementations/dgraph/populateData.json
Original file line number Diff line number Diff line change
@@ -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": "[email protected]",
"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": "[email protected]",
"name": "Jane Smith",
"totalProductsCreated": 1337
}
}],
"deprecatedProd": [{
"sku": "apollo-federation-v1",
"package": "@apollo/federation-v1",
"reason": "Migrate to Federation V2",
"createdBy": {
"email": "[email protected]",
"name": "Jane Smith",
"totalProductsCreated": 1337
}
}]
}
}
54 changes: 54 additions & 0 deletions implementations/dgraph/products.graphql
Original file line number Diff line number Diff line change
@@ -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
}
8 changes: 8 additions & 0 deletions implementations/dgraph/proxy.conf.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
server {
listen 4001;
server_name products;
location / {
proxy_ssl_server_name on;
proxy_pass ${DGRAPH_URL};
}
}
43 changes: 43 additions & 0 deletions implementations/dgraph/resolvers.js
Original file line number Diff line number Diff line change
@@ -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
})
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"]);
};
}

Expand Down

0 comments on commit 38fd701

Please sign in to comment.