diff --git a/benchmark/bun.lockb b/benchmark/bun.lockb index bc51971..075ddf3 100755 Binary files a/benchmark/bun.lockb and b/benchmark/bun.lockb differ diff --git a/benchmark/lambda.ts b/benchmark/lambda.ts index a468c9c..72935e7 100644 --- a/benchmark/lambda.ts +++ b/benchmark/lambda.ts @@ -24,10 +24,10 @@ import benchmarkPayloadProducts from './payload-products.json'; /** * Benchmark configuration values. */ -const COLD_STARTS = 10; -const WARM_STARTS = 10; +const COLD_STARTS = 1; +const WARM_STARTS = 1; // const MEMORY_SIZES = [128, 256, 512, 1024, 2048] as const; -const MEMORY_SIZES = [512, 1024, 2048] as const; +const MEMORY_SIZES = [1024] as const; // How long to wait for XRay to gather all the traces. const WAIT_FOR_XRAY = 15; @@ -281,7 +281,14 @@ const invokeFunctions = async (functionName: string, memorySize: number) => { body: mkPayload(), headers: { 'Content-Type': 'application/json' }, }); - const payload = await res.json(); + let payload: { error: any; errorMessage: any; errorType: any; data: any }; + try { + payload = await res.json(); + } catch (err) { + console.error('Error:', err); + console.error('Response:', res); + process.exit(1); + } const statusCode = res.status; if (statusCode !== 200 || payload.error || payload.errorMessage || payload.errorType || !payload.data) { diff --git a/deployment/bin/helpers.ts b/deployment/bin/helpers.ts index 1a4d68b..cde13c8 100644 --- a/deployment/bin/helpers.ts +++ b/deployment/bin/helpers.ts @@ -1,4 +1,5 @@ import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; /** We want to be able to target a specific stack for build and deployment. We extract * the requested stack from CDK and use that to determine what we should build. @@ -8,13 +9,13 @@ import * as cdk from 'aws-cdk-lib'; * cdk synth --exclusively * ``` */ -export const matchesStack = (app: cdk.App, stackName: string): boolean => { +export const matchesStack = (app: cdk.App | Construct, stackName: string, exactMatch: boolean = false): boolean => { const bundlingStacks = app.node.tryGetContext('aws:cdk:bundling-stacks') as Array; const buildAllStacks = bundlingStacks.includes('**'); const matches = buildAllStacks || bundlingStacks.length === 0 || - bundlingStacks.some((s) => s === stackName || s === `${stackName}/*`); + bundlingStacks.some((s) => s === stackName || (!exactMatch && s === `${stackName}/*`)); if (matches && process.env.VERBOSE) { console.log(`Building stack: ${stackName}`); } diff --git a/deployment/config.ts b/deployment/config.ts index cd133d8..7e07522 100644 --- a/deployment/config.ts +++ b/deployment/config.ts @@ -8,6 +8,7 @@ const base: Config = { runtime: 'lambda', path: '/graphql', pinToVersionedApi: false, + developmentMode: false, }, subgraphs: [ @@ -52,28 +53,7 @@ const development: Config = { runtime: 'lambda', path: '/graphql', pinToVersionedApi: false, - }, - experimental: { - additionalSupergraphs: [ - { - service: 'cosmo', - runtime: 'lambda', - path: '/graphql-cosmo', - pinToVersionedApi: false, - }, - { - service: 'gateway', - runtime: 'lambda', - path: '/graphql-gateway', - pinToVersionedApi: false, - }, - { - service: 'mesh', - runtime: 'lambda', - path: '/graphql-mesh', - pinToVersionedApi: false, - }, - ], + developmentMode: true, }, }; @@ -84,6 +64,7 @@ const production: Config = { runtime: 'lambda', path: '/graphql', pinToVersionedApi: false, + developmentMode: false, }, }; diff --git a/deployment/lib/helpers.ts b/deployment/lib/helpers.ts index aeeae5f..0278fb8 100644 --- a/deployment/lib/helpers.ts +++ b/deployment/lib/helpers.ts @@ -1,3 +1,7 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; + +import { matchesStack } from '../bin/helpers'; import { config as configMap } from '../config'; import { App, Config, Supergraph, validEnvironments } from './types'; @@ -11,8 +15,10 @@ const resolveConfig = (env: string | undefined): Config => { } else if (!validEnvironments.includes(env as any)) { throw new Error(`ENVIRONMENT '${env}' is not a valid option. Possible values ${validEnvironments.join(', ')}`); } else if (env in configMap) { + console.log(`👉 Using '${env}' configuration.`); return configMap[env]; } + console.log("👉 Using 'Base' configuration as no specific environment was set via 'ENVIRONMENT'."); return configMap['Base']; }; @@ -61,7 +67,7 @@ export const setupSupergraph = ) => string, + stackFn: (config: Specific) => string, ) => { const isMainSupergraph = config.supergraph.service === name && config.supergraph.runtime === runtime; // We cast our result to `undefined | Specific` to narrow down the type. @@ -71,11 +77,12 @@ export const setupSupergraph = ); supergraphRoutes[config.supergraph.path] = url; } if (additionalSupergraphConfig) { + const url = stackFn(additionalSupergraphConfig); supergraphRoutes[additionalSupergraphConfig.path] = url; } } @@ -113,3 +120,20 @@ export const setupApp = ( return stackFn(appConfig); } }; + +/** + * Add dependencies to the Supergraph stack and skip dependencies if the Supergraph stack is + * being directly deployed. + * + * Example: + * ```ts + * addSupergraphDependencies(scope, `${id}/${supergraphId}`, supergraph, subgraphs); + * ``` + */ +export const addSupergraphDependencies = (scope: Construct, id: string, supergraph: cdk.Stack, subgraphs: any[]) => { + if (!matchesStack(scope, id, true)) { + subgraphs.forEach((subgraph) => supergraph.addDependency(subgraph)); + } else { + console.log(`👉 Skipping dependencies for ${id} as it is being directly deployed`); + } +}; diff --git a/deployment/lib/services/stack.ts b/deployment/lib/services/stack.ts index 3243279..a273b4a 100644 --- a/deployment/lib/services/stack.ts +++ b/deployment/lib/services/stack.ts @@ -3,7 +3,7 @@ import * as cdk from 'aws-cdk-lib'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import { Construct } from 'constructs'; -import { config, setupApp, setupSupergraph } from '../helpers'; +import { addSupergraphDependencies, config, setupApp, setupSupergraph } from '../helpers'; import * as routerAppRunner from './app-runner'; import * as lambdaFn from './lambda'; import * as s3Website from './s3-website'; @@ -31,7 +31,7 @@ export class Stack extends cdk.Stack { super(scope, id, props); // Collect environment variables pointing to each subgraph URL for the Supergraph. - const subgraphEnvsSsm = {}; + const subgraphEnvsSsm: { [key: string]: string } = {}; // Collect all subgraphs that the supergraph will depend on. const subgraphs: Stack[] = []; @@ -62,7 +62,8 @@ export class Stack extends cdk.Stack { // Set up our Apollo Gateway that pieces together the microservices. setupSupergraph('gateway', 'lambda', supergraphRoutesSsm, (config) => { - const supergraph = new lambdaFn.Stack(this, 'MsGateway', { + const supergraphId = 'MsGateway'; + const supergraph = new lambdaFn.Stack(this, supergraphId, { ...props, functionName: 'ms-gateway', handler: 'lambda.graphqlHandler', @@ -75,13 +76,14 @@ export class Stack extends cdk.Stack { }, }); supergraphs.push(supergraph); - subgraphs.forEach((subgraph) => supergraph.addDependency(subgraph)); + addSupergraphDependencies(scope, `${id}/${supergraphId}`, supergraph, subgraphs); return config?.pinToVersionedApi ? supergraph.aliasUrlParameterName : supergraph.latestUrlParameterName; }); // Set up our GraphQL Mesh that pieces together the microservices. setupSupergraph('mesh', 'lambda', supergraphRoutesSsm, (config) => { - const supergraph = new lambdaFn.Stack(this, 'MsMesh', { + const supergraphId = 'MsMesh'; + const supergraph = new lambdaFn.Stack(this, supergraphId, { ...props, functionName: 'ms-mesh', handler: 'lambda.graphqlHandler', @@ -94,13 +96,14 @@ export class Stack extends cdk.Stack { }, }); supergraphs.push(supergraph); - subgraphs.forEach((subgraph) => supergraph.addDependency(subgraph)); + addSupergraphDependencies(scope, `${id}/${supergraphId}`, supergraph, subgraphs); return config?.pinToVersionedApi ? supergraph.aliasUrlParameterName : supergraph.latestUrlParameterName; }); - // Set up our Apollo Router Lambda that pieces together the microservices. + // Set up our Cosmo Router Lambda that pieces together the microservices. setupSupergraph('router', 'lambda', supergraphRoutesSsm, (config) => { - const supergraph = new lambdaFn.Stack(this, 'MsRouterLambda', { + const supergraphId = 'MsRouterLambda'; + const supergraph = new lambdaFn.Stack(this, supergraphId, { ...props, functionName: 'ms-router', assets: 'artifacts/ms-router', @@ -111,15 +114,26 @@ export class Stack extends cdk.Stack { environmentFromSsm: { ...subgraphEnvsSsm, }, + environment: { + DEV_MODE: config.developmentMode ? 'true' : 'false', + DISABLE_TELEMETRY: 'true', + // FIXME: These are not read by the Lambda Cosmo Router. + GRAPHQL_PATH: '/', + PLAYGROUND_PATH: '/graphiql', + CONFIG_PATH: 'config.yaml', + ROUTER_CONFIG_PATH: 'router.json', + ENGINE_ENABLE_REQUEST_TRACING: 'false', + }, }); supergraphs.push(supergraph); - subgraphs.forEach((subgraph) => supergraph.addDependency(subgraph)); + addSupergraphDependencies(scope, `${id}/${supergraphId}`, supergraph, subgraphs); return config?.pinToVersionedApi ? supergraph.aliasUrlParameterName : supergraph.latestUrlParameterName; }); // Set up our Apollo Router App Runner that pieces together the microservices. setupSupergraph('router', 'app-runner', supergraphRoutesSsm, () => { - const supergraph = new routerAppRunner.Stack(this, 'MsRouterApp', { + const supergraphId = 'MsRouterApp'; + const supergraph = new routerAppRunner.Stack(this, supergraphId, { ...props, repo: 'ms-router', tag: process.env.SUPERGRAPH_ROUTER_IMAGE_TAG ?? `latest`, @@ -131,33 +145,14 @@ export class Stack extends cdk.Stack { memory: appRunner.Memory.HALF_GB, }); supergraphs.push(supergraph); - subgraphs.forEach((subgraph) => supergraph.addDependency(subgraph)); + addSupergraphDependencies(scope, `${id}/${supergraphId}`, supergraph, subgraphs); return supergraph.urlParameterName; }); // Set up our Cosmo Router Lambda that pieces together the microservices. - // setupSupergraph('cosmo', 'lambda', supergraphRoutesSsm, (config) => { - // const supergraph = new lambdaFn.Stack(this, 'MsCosmo', { - // ...props, - // functionName: 'ms-cosmo', - // assets: 'artifacts/ms-cosmo', - // billingGroup: 'ms-cosmo', - // runtime: lambda.Runtime.PROVIDED_AL2023, - // lambdaInsights: false, - // environmentFromSsm: { - // ...subgraphEnvsSsm, - // }, - // environment: { - // PATH_ROUTER: './router', - // }, - // }); - // supergraphs.push(supergraph); - // subgraphs.forEach((subgraph) => supergraph.addDependency(subgraph)); - // return config?.pinToVersionedApi ? supergraph.aliasUrlParameterName : supergraph.latestUrlParameterName; - // }); - setupSupergraph('cosmo', 'lambda', supergraphRoutesSsm, (config) => { - const supergraph = new lambdaFn.Stack(this, 'MsCosmo', { + const supergraphId = 'MsCosmo'; + const supergraph = new lambdaFn.Stack(this, supergraphId, { ...props, functionName: 'ms-cosmo', assets: 'artifacts/ms-cosmo', @@ -174,7 +169,7 @@ export class Stack extends cdk.Stack { }, }); supergraphs.push(supergraph); - subgraphs.forEach((subgraph) => supergraph.addDependency(subgraph)); + addSupergraphDependencies(scope, `${id}/${supergraphId}`, supergraph, subgraphs); return config?.pinToVersionedApi ? supergraph.aliasUrlParameterName : supergraph.latestUrlParameterName; }); diff --git a/deployment/lib/types.ts b/deployment/lib/types.ts index 31d8760..9871704 100644 --- a/deployment/lib/types.ts +++ b/deployment/lib/types.ts @@ -123,6 +123,7 @@ export type Supergraph = runtime: 'lambda'; path: string; pinToVersionedApi: boolean; + developmentMode: boolean; } | { service: 'router'; diff --git a/justfile b/justfile index 477d735..3eebfaa 100644 --- a/justfile +++ b/justfile @@ -6,7 +6,11 @@ router-version := "1.33.1" # The version of the Apollo Router we will be using. Check the latest version at # https://github.com/wundergraph/cosmo/releases -cosmo-version := "0.25.1" +cosmo-version := "0.72.0" + +# The version of the Apollo Router we will be using. Check the latest version at +# https://github.com/wundergraph/cosmo/releases?q=aws-lambda-router&expanded=true +cosmo-lambda-version := "0.3.1" # Display help information. help: @@ -65,6 +69,7 @@ setup-all: @ just deploy-clean @ mkdir -p ./deployment/artifacts just _setup-deployment + just _setup-benchmark just _setup-ui-app just _setup-ui-internal just _setup-ms-gql-users @@ -80,6 +85,9 @@ _setup-deployment: cd deployment/end2end && bun install cd deployment/end2end && bun run setup +_setup-benchmark: + cd benchmark && bun install + _setup-ui-app: cd ui-app && bun install cd ui-app/end2end && bun install @@ -142,6 +150,10 @@ _setup-rust project: rustup toolchain install stable rustup default stable +# Run benchmark tests against the supplied , e.g. `just benchmark 'ms-router,ms-cosmo'`. +benchmark fnNames="ms-router": + cd benchmark && FUNCTION_NAME="{{ fnNames }}" bun run benchmark + # Deploy everything in order. deploy: @ just deploy-stack 'Global/**' @@ -232,10 +244,10 @@ _dev-ui-internal: cd ui-internal && trunk serve _dev-ms-router: - cd ms-router/bin && ./router --anonymous-telemetry-disabled --config ../router-app.yaml --supergraph=../../supergraph.graphql --dev --hot-reload --log debug + SUBGRAPH_USERS_URL=http://127.0.0.1:3065/ SUBGRAPH_PRODUCTS_URL=http://127.0.0.1:3075/ SUBGRAPH_REVIEWS_URL=http://127.0.0.1:3085/ CONFIG_PATH="./ms-router/cosmo.yaml" ROUTER_CONFIG_PATH="supergraph.json" ./ms-router/bin/cosmo -_dev-ms-cosmo-binary: - GRAPH_API_TOKEN='fake' CONFIG_PATH="./ms-router/cosmo.yaml" ROUTER_CONFIG_PATH="supergraph.json" ./ms-router/bin/cosmo +_dev-ms-router-apollo: + cd ms-router/bin && ./router --anonymous-telemetry-disabled --config ../router-app.yaml --supergraph=../../supergraph.graphql --dev --hot-reload --log debug _dev-ms-cosmo: cd ms-cosmo && SUBGRAPH_PRODUCTS_URL=https://ckvmnxg6sssbvn76ghbjfoyg3y0prbgg.lambda-url.eu-west-1.on.aws/ ENGINE_ENABLE_REQUEST_TRACING=false CONFIG_PATH="cosmo.yaml" ROUTER_CONFIG_PATH="supergraph.json" HTTP_PORT=4000 bunx nodemon --watch './**/*.go' --signal SIGTERM --exec 'go' run cmd/main.go @@ -271,7 +283,7 @@ build-all build="release": @ just _build-ms-gql-users {{build}} @ just _build-ms-gql-products {{build}} @ just _build-ms-gql-reviews {{build}} - @ just _build-ms-router-lambda {{build}} + @ just _build-ms-router {{build}} @ just _build-ms-cosmo {{build}} @ just _build-ms-gateway {{build}} @ just _build-ms-mesh {{build}} @@ -311,7 +323,7 @@ _build-ms-gql-reviews build="release": @ rm -r ./deployment/artifacts/ms-gql-reviews || true @ mkdir -p ./deployment/artifacts && cp -r ./target/lambda/ms-gql-reviews ./deployment/artifacts/ms-gql-reviews -_build-ms-router-lambda build="release": +_build-ms-router-apollo build="release": #!/usr/bin/env bash set -euxo pipefail rm -r ./deployment/artifacts/ms-router || true @@ -322,31 +334,33 @@ _build-ms-router-lambda build="release": # Download the prebuilt Apollo Router binary that we will use for deployment. curl -sSL https://github.com/codetalkio/apollo-router-lambda/releases/latest/download/bootstrap-directly-optimized-graviton-arm-{{ if build == "speed" { "speed" } else { "size" } }} -o ./deployment/artifacts/ms-router/bootstrap -_build-ms-cosmo-binary build="release": +_build-ms-router-app build="release": + @ just docker-prepare ms-router + @ just docker-build ms-router + @ just docker-push ms-router + +_build-ms-router build="release": #!/usr/bin/env bash set -euxo pipefail - rm -r ./deployment/artifacts/ms-cosmo || true - mkdir -p ./deployment/artifacts/ms-cosmo - cp ms-router/cosmo.yaml ./deployment/artifacts/ms-cosmo/cosmo.yaml - cp supergraph.json ./deployment/artifacts/ms-cosmo/supergraph.json + rm -r ./deployment/artifacts/ms-router || true + mkdir -p ./deployment/artifacts/ms-router + cp ms-router/cosmo.yaml ./deployment/artifacts/ms-router/cosmo.yaml + cp supergraph.json ./deployment/artifacts/ms-router/supergraph.json - # Download the Cosmo Router binary that we will use for AWS Lambda. + # Download the Cosmo Router binary that we will use for deployment. export TMP_DIR=$(mktemp -d -t "cosmo.XXXXXXXXXX") - curl -sSfL https://github.com/wundergraph/cosmo/releases/download/router%40{{cosmo-version}}/router-router@{{cosmo-version}}-linux-arm64.tar.gz -o $TMP_DIR/cosmo.tar.gz + curl -sSfL https://github.com/wundergraph/cosmo/releases/download/aws-lambda-router%40{{cosmo-lambda-version}}/bootstrap-aws-lambda-router@{{cosmo-lambda-version}}-linux-arm64.tar.gz -o $TMP_DIR/cosmo.tar.gz tar xf $TMP_DIR/cosmo.tar.gz -C $TMP_DIR - mv $TMP_DIR/router ./deployment/artifacts/ms-cosmo/router + mv $TMP_DIR/bootstrap ./deployment/artifacts/ms-router/bootstrap rm -r $TMP_DIR - # Download the prebuilt Router launcher binary that we will use for deployment. - curl -sSL https://github.com/codetalkio/apollo-router-lambda/releases/latest/download/bootstrap-cosmo-arm -o ./deployment/artifacts/ms-cosmo/bootstrap - _build-ms-cosmo build="release": #!/usr/bin/env bash set -euxo pipefail rm -r ./deployment/artifacts/ms-cosmo || true mkdir -p ./deployment/artifacts/ms-cosmo - cp ms-cosmo/cosmo.yaml ./deployment/artifacts/ms-cosmo/cosmo.yaml - cp supergraph.json ./deployment/artifacts/ms-cosmo/supergraph.json + cp ms-cosmo/cosmo.yaml ./deployment/artifacts/ms-cosmo/config.yaml + cp supergraph.json ./deployment/artifacts/ms-cosmo/router.json cd ms-cosmo # Build the go module for Arm64 and without RPC. @@ -354,11 +368,6 @@ _build-ms-cosmo build="release": GOOS=linux GOARCH=arm64 go build -tags lambda.norpc -o bin/bootstrap cmd/main.go cp bin/bootstrap ../deployment/artifacts/ms-cosmo/bootstrap -_build-ms-router-app build="release": - @ just docker-prepare ms-router - @ just docker-build ms-router - @ just docker-push ms-router - docker-prepare project: just _docker-prepare-{{project}} diff --git a/ms-cosmo/cosmo.yaml b/ms-cosmo/cosmo.yaml index f9f0f85..3e73a5d 100644 --- a/ms-cosmo/cosmo.yaml +++ b/ms-cosmo/cosmo.yaml @@ -1,19 +1,26 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/wundergraph/cosmo/router%400.72.0/router/pkg/config/config.schema.json + version: '1' # General router options graph: - name: '' # or FEDERATED_GRAPH_NAME. - token: '' + name: 'router' # or FEDERATED_GRAPH_NAME. + # Token is set via GRAPH_API_TOKEN. -outer_config_path: './supergraph.json' +router_config_path: './router.json' -log_level: 'info' -listen_addr: '127.0.0.1:4005' +log_level: 'debug' +listen_addr: 'localhost:4000' +graphql_path: '/' playground_enabled: true +playground_path: '/graphiql' introspection_enabled: true -# json_log: true +json_log: true # controlplane_url: 'https://cosmo-cp.wundergraph.com' +websocket: + enabled: false + cors: allow_origins: ['*'] allow_methods: @@ -40,6 +47,9 @@ telemetry: prometheus: enabled: false +graphql_metrics: + enabled: false + # Traffic configuration # See "https://cosmo-docs.wundergraph.com/router/traffic-shaping" for more information # traffic_shaping: @@ -73,3 +83,10 @@ headers: named: X-Test-Header - op: 'propagate' matching: (?i)^x-deprecated-.* + +# FIXME: These are not read by the Lambda Cosmo Router. +override_routing_url: + subgraphs: + users: '${SUBGRAPH_USERS_URL}' + products: '${SUBGRAPH_PRODUCTS_URL}' + reviews: '${SUBGRAPH_REVIEWS_URL}' diff --git a/ms-router/cosmo.yaml b/ms-router/cosmo.yaml index 8feafb2..3e73a5d 100644 --- a/ms-router/cosmo.yaml +++ b/ms-router/cosmo.yaml @@ -1,19 +1,26 @@ -ersion: '1' +# yaml-language-server: $schema=https://raw.githubusercontent.com/wundergraph/cosmo/router%400.72.0/router/pkg/config/config.schema.json + +version: '1' # General router options graph: name: 'router' # or FEDERATED_GRAPH_NAME. # Token is set via GRAPH_API_TOKEN. -outer_config_path: './supergraph.json' +router_config_path: './router.json' -log_level: 'info' +log_level: 'debug' listen_addr: 'localhost:4000' +graphql_path: '/' playground_enabled: true +playground_path: '/graphiql' introspection_enabled: true json_log: true # controlplane_url: 'https://cosmo-cp.wundergraph.com' +websocket: + enabled: false + cors: allow_origins: ['*'] allow_methods: @@ -40,6 +47,9 @@ telemetry: prometheus: enabled: false +graphql_metrics: + enabled: false + # Traffic configuration # See "https://cosmo-docs.wundergraph.com/router/traffic-shaping" for more information # traffic_shaping: @@ -73,3 +83,10 @@ headers: named: X-Test-Header - op: 'propagate' matching: (?i)^x-deprecated-.* + +# FIXME: These are not read by the Lambda Cosmo Router. +override_routing_url: + subgraphs: + users: '${SUBGRAPH_USERS_URL}' + products: '${SUBGRAPH_PRODUCTS_URL}' + reviews: '${SUBGRAPH_REVIEWS_URL}'