Although this is a gist with accompanied files, I will at some stage make a proper project with working parts.
This is an opinionated view of how to use Prisma Graphql backend in a microservice way. The idea is that each Graphql Micorservice has both a App GraphQL service (the one that creates a public API) and a Prisma GraphQL service (The one that is a ORM to a DB expiosing GraphQL CRUD, filtering and ordering operations). Each of these microservices are then "stitched" together to form a single GraphQL App Service (Like a Backend For Frontend service BFF) that the client consumes.
- Instructions taken from:
-
https://www.prisma.io/docs/tutorials/bootstrapping-boilerplates/node-phe8vai1oo (Node Boilerplate)
-
https://www.prisma.io/docs/tutorials/bootstrapping-boilerplates/typescript-rohd6ipoo4/ (Typescript Boilerplate)
-
If not installed run
npm install -g prisma graphql-cli
-
open a command prompt / powershell and
cd
to theservices
directory -
run
graphql create MY_DOMAIN --boilerplate node-basic
orgraphql create MY_DOMAIN --boilerplate typescript-basic
- NOTE: replace MY_DOMAIN with the name of the domain you are building (i.e. User, Account)
-
-
Make changes to the prisma code that got generated from boilerplate
-
./database/datamodel.graphql
: Add a aggregated root type matching the domain (and delete the old one). i.e.type User { id: ID! @unique name: String! }
-
./database/seed.graphql
delete or seed with new entity. i.e.mutation { admin: createUser(data: { name: "Admin" }) { id } }
- If deleting also delete the seed reference from
./database/prisma.yml
- If deleting also delete the seed reference from
-
./.graphqlconfig.yml
: Change the name of the app and default endpoint.- app -> user (or the name of the domain service)
- database -> user_db (or the name of the domain service with _db appended)
- http://localhost:4000 -> http://localhost:4001 (or next available one)
projects: user: schemaPath: src/schema.graphql extensions: endpoints: default: http://localhost:4001 user_db: schemaPath: src/generated/prisma.graphql extensions: prisma: database/prisma.yml
-
./database/prisma.yml
change the post-deploy tographql get-schema --project MY_DOMAIN_db
where MY_DOMAIN is the name of the new domain.
-
-
cd MY_DOMAIN
-
run
prisma deploy --force
-
If the generated files do not get updated then need to run (This is fixed in prisma-cli 1.9)
graphql get-schema --project user_db
graphql codegen
for Typescript project
NOTE: Replace user_db with the domain service with _db appended
-
Make changes to the application server code
-
./src/schema.graphql
- Change the import statement to imort the correct type
- Change the Query and Mutation to sensible operations. i.e
type Query { users: [User!]! } type Mutation { createUser(name: String!): User deleteUser(id: ID!): User }
-
./src/index.js or .ts
Change the resolvers to match the schema above.const resolvers = { Query: { users(parent, args, ctx, info) { return ctx.db.query.users(info) }, }, Mutation: { createUser(parent, { name }, ctx, info) { return ctx.db.mutation.createUser( { data: { name, }, }, info, ) }, deleteUser(parent, { id }, ctx, info) { return ctx.db.mutation.deleteUser({ where: { id } }, info) }, }, }
const resolvers = { Query: { users(parent, args, context: Context, info) { return context.db.query.users(info) }, }, Mutation: { createUser(parent, { name }, context: Context, info) { return context.db.mutation.createUser( { data: { name, }, }, info, ) }, deleteUser(parent, { id }, context: Context, info) { return context.db.mutation.deleteUser({ where: { id } }, info) }, }, }
- Also change the way the app starts by injecting the correct port number. Each microservice locally needs a seprate port number
const options = { port: 4001 } server.start(options, ({ port }) => console.log(`Server is running on http://localhost:${port}`))
-
../.graphqlconfig.yml
Update the root level graphql config file to include the new services (server and DB).- Copy the service section from
./.graphqlconfig.yml
into the root config file. i.e:
user: schemaPath: services/user/src/schema.graphql extensions: endpoints: default: http://localhost:4001 user_db: schemaPath: services/user/src/generated/prisma.graphql extensions: prisma: services/user/database/prisma.yml
- Make sure you prepend the microservice folder name to the paths.
- Copy the service section from
-
yarn dev
to start the service and the playground webpage for that microservice
-
Open a console / powershell at each microservice folder level
-
For each microservice:
yarn start
- Open a console / powershell at the root
./services
and run the playground
graphql playground
You should see all the microservices in one place
The one service to rule them all
The idea is that each microservice maintains and owns its own domain (bounded context). Although each microservice is not aware of another microservices it is desirable to have a single application that combines the functionality of each microservces together.
Schema Stiching allows this to happen by combining multiple graphql services into a uber graphql service
This is only really done once per application.
Stepes to create a BFF Service are still in a TODO state
Needs to be added to the root .graphqlconfig.yml file
projects:
app:
extensions:
endpoints:
default: http://localhost:4000
With each new microservice they need to be added to this BFF service.
./app/server.js
add the URL from the microservice to the endpoint object
const endpoints = [
'http://localhost:4001',
'http://localhost:4002'
];
yarn start
A single BFF service that just exposes the entities and operations up until now is just a convienent way to avoid service discovery and multiple round trips
However the queries can be a little disconnected with eachother and if the result of one query needs to be fed into another then multiple round trips will be required.
ideally we should be able to write
{
users {
id, name
accounts {
id, name
}
}
}
Which should return all the accounts for a given user. To achieve this we need to stitch the entities together as a relation
To stitch 2 entities together that are not part of the same service (this could also work on the same service) you need to do 2 things
- extend the parent type with a new field with the type from the second entity
- resolve the relationship by joining a parent field to the child field
const { makeRelations, RelationType } = require('./stitch');
const { extendedSchema, resolvers } = makeRelations([
{
parent: 'User.id',
child: 'Account.userId',
parentField: 'accounts',
relationType: RelationType.OneToMany,
}
]);
Ideally split src/index.js or .ts
into two
- The server running the code
- queries and mutations that make the service
Note that this method uses the serverless framework to upload the Lambda
The following are steps to get the code ready for serverless
- Make the file
./index.js
(root folder location of the service) - Change
./package.json
and change the script to removesrc
"start": "node index.js or .ts",
- Move the
GraphQLServer
code from./src/index.js and copy to
./index.js`- Bring in imports also
- Add new import to
./src/resolvers.js
- Correct the paths.
- Change the filename
./src/index.js or .ts
to./src/resolvers.js or .ts
- Also export the resolvers
exports.resolvers = {...}
- Also export the resolvers
Prereq: install serverless
npm install -g serverless
Add the necesasary boilerplate code:
- Easiest is to copy the example from github
https://github.com/prismagraphql/graphql-yoga/tree/master/examples/lambda
-
Change
./handler.js
to import the resolvers as we did above -
Set the typedefs to that of
./index.js
-
Set the context.
-
Set the resolvers.
-
Check the Profile being used !! IMPORTANT IF USING DIFFERENT PROFILES !!
aws configure
- Verify that the correct account / profile is being used.
- If not set correctly:
$env:AWS_PROFILE = "PROFILENAME"
which matches.aws/credentials
file- If there is no user, the administrator needs to create one with the right permissions. See Setting the Permissions below
-
serverless deploy
The profile that is being used need access to AWS resources. If you are not using the root user (you shouldn't) the user needs to have the correct policies for the serverless framework to work. The following is what I did (may expose a bit too much until I spend more time refining it)
-
Log into AWS using the root account
-
In AWS IAM create a group
Serverless
(name it whatever you want) -
In that group set the following AWS premade policies.
- AWSLambdaFullAccess
- AmazonAPIGatewayInvokeFullAccess
- AmazonAPIGatewayAdministrator <- may need to change this
- AWSCloudFormationReadOnlyAccess
-
You need to create a custom policy that are not provided by the premade ones from AWS.
- Create 3 new Policies setting the JSON to... (could be merged intop one) - see below
- Add them to the group
-
Add the same User to the group that is being used to deploy the serverless application
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": "iam:CreateRole",
"Resource": "arn:aws:iam::*:role/*-lambdaRole"
}
}
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"cloudformation:ListStacks",
"cloudformation:CreateStack",
"cloudformation:UpdateStack",
"cloudformation:DescribeStackResource",
"cloudformation:CreateChangeSet",
"cloudformation:DescribeChangeSet",
"cloudformation:ExecuteChangeSet",
"cloudformation:ValidateTemplate",
"cloudformation:DescribeStacks",
"cloudformation:DeleteStack"
],
"Resource": "*"
}
]
}
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": "iam:PutRolePolicy",
"Resource": "*"
}
}