Skip to content

Commit

Permalink
feat(dataloader): create an example app that uses Apollo, koa, a data…
Browse files Browse the repository at this point in the history
…loader, and Mongo or DynamoDB

Except for the dataloader, these are all things we instrument, so we
should be seeing spans for all of them once our instrumentation is
working.

And as far as I can tell... we do.
  • Loading branch information
Jordi Gutiérrez Hermoso committed May 18, 2023
1 parent 612efdd commit 6a09089
Show file tree
Hide file tree
Showing 17 changed files with 5,410 additions and 0 deletions.
11 changes: 11 additions & 0 deletions graphql-koa-dataloader/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM node:lts-slim
EXPOSE 4000

RUN apt-get update && apt-get install httpie jq -y

WORKDIR /app
COPY app .
COPY entrypoint.sh /entrypoint.sh
RUN npm ci

ENTRYPOINT ["/entrypoint.sh"]
7 changes: 7 additions & 0 deletions graphql-koa-dataloader/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
This is an example of an Apollo server using the Koa middleware and dataloader

Get it running:

1. Copy sample.env to .env
2. Edit .env to add your New Relic ingest key, and any other desired changes.
3. Start with `docker-compose up --build`.
17 changes: 17 additions & 0 deletions graphql-koa-dataloader/app/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright 2022 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

module.exports = {
extends: '@newrelic',
parserOptions: {
ecmaVersion: 'latest'
},
rules: {
'no-console': 'off',
'func-names': 'off'
}
}
26 changes: 26 additions & 0 deletions graphql-koa-dataloader/app/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2023 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import DataLoader from 'dataloader'
import { getUserById } from './datastore.js'

const loaders = () => ({
getUserById: new DataLoader(
(ids) => {
return Promise.all(ids.map((id) => getUserById(Number(id))))
},
{
batchScheduleFn: (callback) => setTimeout(callback, 100)
}
)
})

const getContext = () => {
return {
loaders: loaders()
}
}

export default getContext
78 changes: 78 additions & 0 deletions graphql-koa-dataloader/app/datastore-dynamodb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright 2023 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import {
DynamoDBClient as Client,
CreateTableCommand,
DeleteTableCommand,
waitUntilTableExists,
waitUntilTableNotExists
} from '@aws-sdk/client-dynamodb'
import { DynamoDBDocumentClient, GetCommand, PutCommand } from '@aws-sdk/lib-dynamodb'
import { generateUsers } from './utils.js'

const tableParams = {
AttributeDefinitions: [
{
AttributeName: 'id',
AttributeType: 'N'
}
],
KeySchema: [
{
AttributeName: 'id',
KeyType: 'HASH'
}
],
ProvisionedThroughput: {
ReadCapacityUnits: 1,
WriteCapacityUnits: 1
},
TableName: 'Users',
StreamSpecification: {
StreamEnabled: false
}
}

async function initData() {
const client = new Client({})
try {
console.log('Deleting table...')
await client.send(new DeleteTableCommand({ TableName: 'Users' }))
await waitUntilTableNotExists({ client }, { TableName: 'Users' })
} catch (err) {
console.log(err.message)
}
console.log('Creating table...')
await client.send(new CreateTableCommand(tableParams))
await waitUntilTableExists({ client }, { TableName: 'Users' })

console.log('Adding users...')
const docClient = DynamoDBDocumentClient.from(client)
const users = generateUsers(10)
for (const user of users) {
user.id = user._id
await docClient.send(
new PutCommand({
TableName: 'Users',
Item: user
})
)
}
console.log('added 10 users')
}

async function getUserById(id) {
const client = DynamoDBDocumentClient.from(new Client({}))
const { Item: item } = await client.send(
new GetCommand({
TableName: 'Users',
Key: { id }
})
)
return item
}

export { getUserById, initData }
40 changes: 40 additions & 0 deletions graphql-koa-dataloader/app/datastore-mongo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2023 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import { MongoClient } from 'mongodb'
import { generateUsers } from './utils.js'

async function initData() {
const client = new MongoClient(process.env.MONGO_URL)
try {
await client.connect()
const allUsers = generateUsers(10)
const db = client.db('users')
const friends = db.collection('friends')
await friends.deleteMany()

const result = await friends.insertMany(allUsers, { ordered: true })
console.log(`${result.insertedCount} users were created`)
} finally {
client.close()
}
}

async function getUserById(id) {
console.log(`Fetch user #${id}`)
const client = new MongoClient(process.env.MONGO_URL)
try {
await client.connect()
const db = client.db('users')
const friends = db.collection('friends')
const result = await friends.findOne({ _id: id })
console.log(result)
return result
} finally {
client.close()
}
}

export { getUserById, initData }
33 changes: 33 additions & 0 deletions graphql-koa-dataloader/app/datastore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2023 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import * as dynamodb from './datastore-dynamodb.js'
import * as mongo from './datastore-mongo.js'

async function initData() {
if (process.env.NEW_RELIC_DATASTORE.toLowerCase() === 'dynamodb') {
return await dynamodb.initData()
}
if (process.env.NEW_RELIC_DATASTORE.toLowerCase() === 'mongo') {
return await mongo.initData()
}
throw new Error(
'Unknown datastore. Set the "NEW_RELIC_DATASTORE" environment variable to either "DynamoDB" or "Mongo"'
)
}

async function getUserById(id) {
if (process.env.NEW_RELIC_DATASTORE.toLowerCase() === 'dynamodb') {
return await dynamodb.getUserById(id)
}
if (process.env.NEW_RELIC_DATASTORE.toLowerCase() === 'mongo') {
return await mongo.getUserById(id)
}
throw new Error(
'Unknown datastore. Set the "NEW_RELIC_DATASTORE" environment variable to either "DynamoDB" or "Mongo"'
)
}

export { getUserById, initData }
18 changes: 18 additions & 0 deletions graphql-koa-dataloader/app/make-requests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash
shopt -s expand_aliases

alias http='http --print b --ignore-stdin'

echo "Starting request loop"
while true; do

# Get all users
echo "Fetching all users"
http localhost:4000 query='{allUsers{age, email, hobbies, id, name, friends}}' | jq -C

for id in {1..10}; do
echo "Fetching user #${id}"
http localhost:4000 query="{user(id:${id}){age, email, hobbies, id, name, friends}}" | jq -C
done
sleep 5
done
Loading

0 comments on commit 6a09089

Please sign in to comment.