Skip to content

Commit

Permalink
Add dataloader pattern (#159)
Browse files Browse the repository at this point in the history
This addresses #158 by using the dataloader pattern to batch and memoize entity calls within the same request.

The design followed here introduces the concept of an "execution context" or Executor. An Executor instance is unique to the request, and is to be used as a key to WeakMap caches of entities.

Special care must be taken to avoid reusing stale data after a mutation. Accordingly, the Executor instance is replaced in the broader context object by each mutation. Because GraphQL guarantees that mutations are run serially, we don't have to worry about race conditions here.
  • Loading branch information
mike-marcacci authored Aug 14, 2020
1 parent 9078527 commit f2d5988
Show file tree
Hide file tree
Showing 131 changed files with 5,237 additions and 4,794 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
{
"parser": "@typescript-eslint/parser",
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier",
"prettier/@typescript-eslint"
],
"rules": {
"@typescript-eslint/ban-types": [1],
"@typescript-eslint/no-explicit-any": [0],
"@typescript-eslint/no-use-before-define": [0],
"@typescript-eslint/no-inferrable-types": [0],
Expand Down
2 changes: 2 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{
"trailingComma": "none",
"arrowParens": "avoid",
"overrides": [
{
"files": ".ts",
Expand Down
8 changes: 4 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ before_install:
- sudo /etc/init.d/postgresql stop
- sudo service mysql stop
install:
- docker-compose up -d
- docker-compose up -d postgres
script:
- docker-compose exec authx yarn build
- docker-compose exec authx yarn lint
- docker-compose exec authx yarn test
- docker-compose run runner yarn build
- docker-compose run runner yarn lint
- docker-compose run runner yarn test
91 changes: 79 additions & 12 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,19 +1,86 @@
version: "3"
version: "3.7"

volumes:
postgres:
server_node_modules:

services:
authx:
build: .

postgres:
image: postgres:9.6.17
restart: always
environment:
POSTGRES_PASSWORD: postgres
volumes:
- type: volume
source: postgres
target: /var/lib/postgres
ports:
- "80:80"
- "9229:9229"
- target: 5432
protocol: tcp
mode: localhost

# This container doesn't do anything by itself, but can be used to run tests
# or issue one-off commands.
runner:
build:
context: .
target: base
environment:
NODE_ENV: development
PGHOST: postgres
PGUSER: postgres
PGPASSWORD: authx
links:
- postgres
postgres:
image: postgres:11
PGPASSWORD: postgres
volumes:
- type: bind
source: .
target: /workspace
- type: volume
source: server_node_modules
target: /workspace/node_modules
ports:
- "5432:5432"
- target: 80
protocol: tcp
mode: localhost

# This container builds the server.
builder:
build:
context: .
target: base
command: yarn build:development
environment:
POSTGRES_PASSWORD: authx
NODE_ENV: development
volumes:
- type: bind
source: .
target: /workspace
- type: volume
source: server_node_modules
target: /workspace/node_modules

# This container runs the server.
server:
depends_on:
- builder
- postgres
build:
context: .
target: base
command: yarn start:development
environment:
NODE_ENV: development
PGHOST: postgres
PGUSER: postgres
PGPASSWORD: postgres
volumes:
- type: bind
source: .
target: /workspace
- type: volume
source: server_node_modules
target: /workspace/node_modules
ports:
- target: 80
protocol: tcp
mode: localhost
File renamed without changes.
31 changes: 14 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,24 @@
"root:lint": "prettier -c '**/*.{json,yml,md,ts}' && eslint src --ext ts",
"root:test": "ava --verbose dist/*.test.js",
"root:test:development": "ava --verbose --watch dist/*.test.js",
"build": "yarn packages:build && yarn root:build",
"build": "yarn packages:build && echo '\n\n ---------- ' && yarn root:build",
"build:development": "yarn packages:build:development & yarn root:build:development",
"format": "yarn packages:format && yarn root:format",
"lint": "yarn packages:lint && yarn root:lint",
"format": "yarn packages:format && echo '\n\n ---------- ' && yarn root:format",
"lint": "yarn packages:lint && echo '\n\n ---------- ' && yarn root:lint",
"start": "node dist/server",
"start:development": "nodemon --delay 2 --inspect dist/server",
"test": "yarn packages:test && yarn root:test"
"test": "yarn packages:test && echo '\n\n ---------- ' && yarn root:test"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^2.22.0",
"@typescript-eslint/parser": "^2.22.0",
"ava": "^3.5.0",
"eslint": "^6.7.2",
"eslint-config-prettier": "^6.10.0",
"eslint-plugin-prettier": "^3.1.1",
"koa": "^2.11.0",
"nodemon": "^2.0.1",
"prettier": "^1.19.1",
"typescript": "^3.8.3"
},
"resolutions": {
"**/graphql": "15.0.0-rc.2"
"@typescript-eslint/eslint-plugin": "^3.9.0",
"@typescript-eslint/parser": "^3.9.0",
"ava": "^3.11.1",
"eslint": "^7.6.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.4",
"koa": "^2.13.0",
"nodemon": "^2.0.4",
"prettier": "^2.0.5",
"typescript": "^3.9.7"
}
}
50 changes: 25 additions & 25 deletions packages/authx/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,40 @@
"@types/auth-header": "^1.0.1",
"@types/form-data": "^2.5.0",
"@types/graphql-api-koa": "^2.0.2",
"@types/graphql-relay": "^0.4.11",
"@types/jsonwebtoken": "^8.3.8",
"@types/koa": "2.11.2",
"@types/koa-router": "^7.0.42",
"@types/graphql-relay": "^0.6.0",
"@types/jsonwebtoken": "^8.5.0",
"@types/koa": "2.11.3",
"@types/koa-router": "^7.4.1",
"@types/koa-send": "^4.1.2",
"@types/pg": "^7.14.1",
"@types/uuid": "^7.0.0",
"@types/object-hash": "^1.3.3",
"@types/pg": "^7.14.4",
"@types/uuid": "^8.3.0",
"auth-header": "^1.0.0",
"dataloader": "^2.0.0",
"form-data": "^3.0.0",
"graphql": "15.0.0-rc.2",
"graphql-api-koa": "^4.1.1",
"graphql-playground-middleware-koa": "^1.6.12",
"graphql": "15.3.0",
"graphql-api-koa": "^6.0.0",
"graphql-playground-middleware-koa": "^1.6.17",
"graphql-relay": "^0.6.0",
"jsonwebtoken": "^8.5.0",
"koa": "^2.11.0",
"koa-body": "^4.1.1",
"koa-router": "^8.0.8",
"koa": "^2.13.0",
"koa-body": "^4.2.0",
"koa-router": "^9.4.0",
"node-fetch": "^2.5.0",
"pg": "^7.18.2",
"uuid": "^7.0.2"
"object-hash": "^2.0.3",
"pg": "^8.3.0",
"uuid": "^8.3.0"
},
"description": "",
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^2.22.0",
"@typescript-eslint/parser": "^2.22.0",
"ava": "^3.5.0",
"eslint": "^6.7.2",
"eslint-config-prettier": "^6.10.0",
"@typescript-eslint/eslint-plugin": "^3.9.0",
"@typescript-eslint/parser": "^3.9.0",
"ava": "^3.5.2",
"eslint": "^7.6.0",
"eslint-config-prettier": "^6.10.1",
"eslint-plugin-prettier": "^3.1.1",
"nodemon": "^2.0.2",
"prettier": "^1.19.1",
"prettier": "^2.0.2",
"typescript": "^3.8.3"
},
"engines": {
Expand All @@ -62,7 +65,7 @@
"main": "dist",
"name": "@authx/authx",
"optionalDependencies": {
"@opencensus/core": "^0.0.20",
"@opencensus/core": "^0.0.22",
"graphql-middleware": "^4.0.2"
},
"peerDependencies": {
Expand All @@ -78,8 +81,5 @@
"test:development": "ava --verbose --watch dist/**/*.test.js"
},
"types": "dist/index.d.ts",
"version": "3.1.0-alpha.10",
"resolutions": {
"**/graphql": "15.0.0-rc.2"
}
"version": "3.1.0-alpha.10"
}
2 changes: 2 additions & 0 deletions packages/authx/scopes.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from "./dist/util/scopes";
export * from "./dist/util/createV2AuthorityAdministrationScopes";
export * from "./dist/util/createV2CredentialAdministrationScopes";
6 changes: 5 additions & 1 deletion packages/authx/scopes.js
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
module.exports = require("./dist/util/scopes");
module.exports = {
...require("./dist/util/scopes"),
...require("./dist/util/createV2AuthorityAdministrationScopes"),
...require("./dist/util/createV2CredentialAdministrationScopes"),
};
5 changes: 2 additions & 3 deletions packages/authx/src/Context.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Pool } from "pg";
import { Authorization } from "./model";
import { StrategyCollection } from "./StrategyCollection";
import { Explanation } from "./util/explanations";
import { ReadonlyDataLoaderExecutor } from "./loader";

export interface Context {
readonly realm: string;
Expand All @@ -17,8 +17,7 @@ export interface Context {
readonly html: string;
readonly from?: string;
}) => Promise<any>;
readonly pool: Pool;
readonly strategies: StrategyCollection;
readonly explanations: ReadonlyArray<Explanation>;
executor: ReadonlyDataLoaderExecutor<Pool>;
authorization: null | Authorization;
}
17 changes: 12 additions & 5 deletions packages/authx/src/StrategyCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,37 @@ export class StrategyCollection {
public map: { [name: string]: Strategy } = {};

public authorityMap: {
readonly [name: string]: { new (data: AuthorityData<any>): Authority<any> };
readonly [name: string]: {
new (data: AuthorityData<any> & { readonly recordId: string }): Authority<
any
>;
};
} = {};

public credentialMap: {
readonly [name: string]: {
new (data: CredentialData<any>): Credential<any>;
new (
data: CredentialData<any> & { readonly recordId: string }
): Credential<any>;
};
} = {};

public queryFields: {
readonly [field: string]: GraphQLFieldConfig<any, any, Context>;
readonly [field: string]: GraphQLFieldConfig<any, Context, any>;
} = {};

public mutationFields: {
readonly [field: string]: GraphQLFieldConfig<any, any, Context>;
readonly [field: string]: GraphQLFieldConfig<any, Context, any>;
} = {};

public types: GraphQLNamedType[] = [];

public constructor(strategies?: Iterable<Strategy>) {
if (strategies)
if (strategies) {
for (const strategy of strategies) {
this.add(strategy);
}
}
}

public add(s: Strategy): StrategyCollection {
Expand Down
12 changes: 8 additions & 4 deletions packages/authx/src/apolloTracing.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { responsePathAsArray, GraphQLResolveInfo } from "graphql";
import {
responsePathAsArray,
GraphQLResolveInfo,
GraphQLFieldResolver
} from "graphql";

type HighResolutionTime = [number, number];

Expand Down Expand Up @@ -40,9 +44,9 @@ function durationHrTimeToNanos(hrtime: HighResolutionTime): number {
}

export async function apolloTracingGraphQLMiddleware(
resolve: Function,
parent: any,
args: any,
resolve: GraphQLFieldResolver<any, any, any>,
parent: unknown,
args: unknown,
context: { [apolloTracingContext]: ApolloTracingContext },
info: GraphQLResolveInfo
): Promise<any> {
Expand Down
Loading

0 comments on commit f2d5988

Please sign in to comment.