Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/allow multi federation #4

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Example of serveral federated services
======================================
This example shows 2 postgraphile services federated with a appolo-server service all federated through @apollo-gateway.


Setup
-----
1) Create the databses:
`$> createdb accountsdb`
`$> createdb productsdb`

2) run the database creation scripts - THIS WILL CREATE ROLES ON YOUR SERVER
`$> cat ./accounts/database.sql > psql -d accountsdb`
`$> cat ./products/database.sql > psql -d products`

3) Setup project (make sure you have built federation package):
`$> yarn install`
`$> yarn start`

4) Navigate to https://localhost:3001

Example Query:
```graphql
query {
purchase(purchaseId: 2) {
account {
firstName,
purchases {
purchaseId,
product {
reviews {
body
}
}
}
}
}
}
```
17 changes: 17 additions & 0 deletions examples/accounts/database.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
CREATE TABLE account (
account_id SERIAL PRIMARY KEY,
first_name VARCHAR NOT NULL,
last_name VARCHAR NOT NULL
);

INSERT INTO account (first_name, last_name) VALUES
('Charlotte', 'Williams'),
('Jessica', 'Rodriguez'),
('Emily', 'Davis'),
('Jan', 'Mata'),
('Michael', 'Williams'),
('Jules', 'Michael');

CREATE ROLE accountsuser WITH LOGIN ENCRYPTED PASSWORD 'jw8s0F4';
GRANT ALL ON ALL TABLES IN SCHEMA public TO accountsuser;

24 changes: 24 additions & 0 deletions examples/accounts/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const express = require("express");
const { postgraphile } = require("postgraphile");
const FederationPlugin = require("@graphile/federation").default;
const PgSimplifyInflectorPlugin = require("@graphile-contrib/pg-simplify-inflector");
const { NodePlugin } = require("graphile-build");

const DATABASE_URL =
"postgresql://accountsuser:jw8s0F4@localhost:5432/accountsdb";

const app = express();
app.use(
postgraphile(DATABASE_URL, "public", {
appendPlugins: [PgSimplifyInflectorPlugin, FederationPlugin],
graphiql: true,
watchPg: true,
skipPlugins: [NodePlugin],
graphileBuildOptions: {
pgOmitListSuffix: true,
pgShortPk: true,
},
})
);

app.listen(process.env.PORT || 3002);
52 changes: 52 additions & 0 deletions examples/gateway/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const { ApolloGateway, RemoteGraphQLDataSource } = require("@apollo/gateway");
const { ApolloServer } = require("apollo-server");
var waitOn = require("wait-on");

class AuthenticatedDataSource extends RemoteGraphQLDataSource {
constructor(config) {
super(config);
}

willSendRequest({ request, context }) {
request.http.headers.set("x-account-id", context.account);
request.http.headers.set("x-organization-id", context.organization);
}
}

async function main() {
await waitOn({
resources: [
"http://localhost:3002/graphiql",
"http://localhost:3003/graphiql",
"http://localhost:3004/.well-known/apollo/server-health",
],
});

const gateway = new ApolloGateway({
serviceList: [
{ name: "accounts", url: "http://localhost:3002/graphql" },
{ name: "products", url: "http://localhost:3003/graphql" },
{ name: "reviews", url: "http://localhost:3004/" },
],
buildService({ url }) {
return new AuthenticatedDataSource({ url });
},
});

const server = new ApolloServer({
gateway,
subscriptions: false,
context: ({ req }) => {
return {
account: req.headers["x-account-id"],
organization: req.headers["x-organization-id"],
};
},
});

server.listen(3001).then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
}

main();
35 changes: 35 additions & 0 deletions examples/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "examples",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"private": true,
"dependencies": {
"@apollo/federation": "^0.10.2",
"@apollo/gateway": "^0.10.8",
"@graphile-contrib/pg-simplify-inflector": "^5.0.0-beta.1",
"@graphile/federation": "../",
"apollo-server": "^2.9.7",
"express": "^4.17.1",
"graphile-build": "^4.4.5",
"graphile-build-pg": "^4.4.5",
"graphile-utils": "^4.4.5",
"graphql": "14.5.8",
"pg": "^7.12.1",
"postgraphile": "^4.4.4",
"wait-on": "^3.3.0"
},
"devDependencies": {
"concurrently": "^5.0.0"
},
"resolutions": {
"graphql": "14.5.8"
},
"scripts": {
"start": "concurrently yarn:start-*",
"start-accounts": "node ./accounts/index.js",
"start-products": "node ./products/index.js",
"start-gateway": "node ./gateway/index.js",
"start-reviews": "node ./reviews/index.js"
}
}
39 changes: 39 additions & 0 deletions examples/products/database.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
CREATE TABLE product (
product_id SERIAL PRIMARY KEY,
product_name VARCHAR NOT NULL,
price VARCHAR NOT NULL
);

CREATE TABLE purchase (
purchase_id SERIAL PRIMARY KEY,
product_id INT REFERENCES product(product_id),
account_id INT NOT NULL,
date_purchased TIMESTAMP NOT NULL
);

COMMENT ON COLUMN purchase.account_id IS E'@name account\n@federated Account(accountId)';

INSERT INTO product (product_name, price) VALUES
('Book 1', '2.99'),
('Book 2', '3.99'),
('Book 3', '7.99'),
('Book 4', '12.49'),
('Toy Car 1', '2.49'),
('Toy Car 2', '2.79');

INSERT INTO purchase (product_id, account_id, date_purchased) VALUES
(2, 1, '2019-10-01 09:45:00'),
(2, 2, '2019-10-01 12:22:00'),
(3, 2, '2019-10-02 11:48:00'),
(3, 3, '2019-10-02 20:39:00'),
(3, 5, '2019-10-02 22:12:00'),
(4, 2, '2019-10-04 12:34:00'),
(4, 3, '2019-10-04 14:25:00'),
(5, 3, '2019-10-04 21:42:00'),
(5, 3, '2019-10-09 07:25:00'),
(5, 4, '2019-10-09 09:11:00'),
(5, 5, '2019-10-09 11:26:00'),
(6, 3, '2019-10-09 14:53:00');

CREATE ROLE productsuser WITH LOGIN ENCRYPTED PASSWORD 'jw8s0F4';
GRANT ALL ON ALL TABLES IN SCHEMA public TO productsuser;
58 changes: 58 additions & 0 deletions examples/products/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const express = require("express");
const { postgraphile } = require("postgraphile");
const FederationPlugin = require("@graphile/federation").default;
const PgSimplifyInflectorPlugin = require("@graphile-contrib/pg-simplify-inflector");
const { makeExtendSchemaPlugin, gql } = require("graphile-utils");
const { NodePlugin } = require("graphile-build");

const ExtensionPlugin = makeExtendSchemaPlugin(build => {
const { pgSql: sql } = build;
return {
typeDefs: gql`
type Account @key(fields: "accountId") @extends {
accountId: Int! @external
purchases: [Purchase!]
}
`,
resolvers: {
Query: {},
Account: {
purchases: async (parent, args, context, resolveInfo) => {
return resolveInfo.graphile.selectGraphQLResultFromTable(
sql.fragment`public.purchase`,
(tableAlias, queryBuilder) => {
queryBuilder.where(
sql.fragment`${tableAlias}.account_id=${sql.value(
parent.accountId
)}`
);
}
);
},
},
},
};
});

const DATABASE_URL =
"postgresql://productsuser:jw8s0F4@localhost:5432/productsdb";

const app = express();
app.use(
postgraphile(DATABASE_URL, "public", {
appendPlugins: [
ExtensionPlugin,
PgSimplifyInflectorPlugin,
FederationPlugin,
],
graphiql: true,
watchPg: true,
skipPlugins: [NodePlugin],
graphileBuildOptions: {
pgOmitListSuffix: true,
pgShortPk: true,
},
})
);

app.listen(process.env.PORT || 3003);
57 changes: 57 additions & 0 deletions examples/reviews/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const { ApolloServer, gql } = require("apollo-server");
const { buildFederatedSchema } = require("@apollo/federation");

const typeDefs = gql`
type Review {
reviewId: Int!
body: String!
author: Account!
product: Product!
}

extend type Account @key(fields: "accountId") {
accountId: Int! @external
reviews: [Review!]
}

extend type Product @key(fields: "productId") {
productId: Int! @external
reviews: [Review!]
}
`;

const reviews = [
{ productId: 2, accountId: 1, body: "Great Purchase" },
{ productId: 3, accountId: 3, body: "Very Happy" },
{ productId: 3, accountId: 5, body: "Could Be Better" },
{ productId: 4, accountId: 2, body: "Fabulous" },
{ productId: 5, accountId: 3, body: "Awful" },
{ productId: 6, accountId: 3, body: "Perfect" },
];

const resolvers = {
Query: {},
Review: {
__resolveReference(review) {
return reviews[review.reviewId];
},
},
Account: {
reviews: account => {
return reviews.filter(x => x.accountId === account.accountId);
},
},
Product: {
reviews: product => {
return reviews.filter(x => x.productId === product.productId);
},
},
};

const server = new ApolloServer({
schema: buildFederatedSchema([{ typeDefs, resolvers }]),
});

server.listen(3004).then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
Loading