From cc10cba92f45466edebb0ab39bd35702841f916a Mon Sep 17 00:00:00 2001 From: Ben Bangert Date: Fri, 15 Sep 2023 10:54:47 -0700 Subject: [PATCH] feat(payments): add initial NestJS service load on heartbeat Because: * We want to load/cache the NestJS services on the first request to the app, so that the first request is not slow. * We should use proper NextJS project organization. This commit: * Adds a new route to the app that the loadbalancers utilize, to ensure that the NestJS services are loaded before the first request to the app. * Moves internal implementation that shouldn't be routed to, behind the underscore directory prefix. --- .../next/app/%5F_heartbeat__/route.ts | 16 +++++++++++ .../next/app/%5F_lbheartbeat__/route.ts | 16 +++++++++++ .../next/app/[offeringId]/checkout/page.tsx | 9 +++--- .../next/app/{lib => _lib}/apiClient.ts | 0 apps/payments/next/app/{lib => _lib}/stubs.ts | 0 .../app/{nestapp => _nestapp}/app.module.ts | 4 +-- apps/payments/next/app/_nestapp/app.ts | 28 +++++++++++++++++++ .../next/app/{nestapp => _nestapp}/config.ts | 0 apps/payments/next/app/nestapp/app.ts | 17 ----------- libs/shared/db/mysql/account/src/index.ts | 2 +- .../mysql/account/src/lib/account.provider.ts | 2 +- 11 files changed, 69 insertions(+), 25 deletions(-) create mode 100644 apps/payments/next/app/%5F_heartbeat__/route.ts create mode 100644 apps/payments/next/app/%5F_lbheartbeat__/route.ts rename apps/payments/next/app/{lib => _lib}/apiClient.ts (100%) rename apps/payments/next/app/{lib => _lib}/stubs.ts (100%) rename apps/payments/next/app/{nestapp => _nestapp}/app.module.ts (82%) create mode 100644 apps/payments/next/app/_nestapp/app.ts rename apps/payments/next/app/{nestapp => _nestapp}/config.ts (100%) delete mode 100644 apps/payments/next/app/nestapp/app.ts diff --git a/apps/payments/next/app/%5F_heartbeat__/route.ts b/apps/payments/next/app/%5F_heartbeat__/route.ts new file mode 100644 index 00000000000..8d567d8581c --- /dev/null +++ b/apps/payments/next/app/%5F_heartbeat__/route.ts @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { NextResponse } from 'next/server'; + +import { app } from '../_nestapp/app'; + +export const dynamic = 'force-dynamic'; + +export async function GET(request: Request) { + await app.getApp(); + const resp = new NextResponse('{}'); + resp.headers.set('Content-Type', 'application/json'); + return resp; +} diff --git a/apps/payments/next/app/%5F_lbheartbeat__/route.ts b/apps/payments/next/app/%5F_lbheartbeat__/route.ts new file mode 100644 index 00000000000..8d567d8581c --- /dev/null +++ b/apps/payments/next/app/%5F_lbheartbeat__/route.ts @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { NextResponse } from 'next/server'; + +import { app } from '../_nestapp/app'; + +export const dynamic = 'force-dynamic'; + +export async function GET(request: Request) { + await app.getApp(); + const resp = new NextResponse('{}'); + resp.headers.set('Content-Type', 'application/json'); + return resp; +} diff --git a/apps/payments/next/app/[offeringId]/checkout/page.tsx b/apps/payments/next/app/[offeringId]/checkout/page.tsx index 773f11f811f..59280801097 100644 --- a/apps/payments/next/app/[offeringId]/checkout/page.tsx +++ b/apps/payments/next/app/[offeringId]/checkout/page.tsx @@ -1,13 +1,14 @@ -import { CartService } from '@fxa/payments/cart'; import { PurchaseDetails, TermsAndPrivacy } from '@fxa/payments/ui/server'; -import { getCartData, getContentfulContent } from '../../lib/apiClient'; -import { getApp } from '../../nestapp/app'; +import { getCartData, getContentfulContent } from '../../_lib/apiClient'; +import { app } from '../../_nestapp/app'; interface CheckoutParams { offeringId: string; } +export const dynamic = 'force-dynamic'; + export default async function Index({ params }: { params: CheckoutParams }) { // TODO - Fetch Cart ID from cookie // https://nextjs.org/docs/app/api-reference/functions/cookies @@ -21,7 +22,7 @@ export default async function Index({ params }: { params: CheckoutParams }) { const cartData = getCartData(cartId); const [contentful, cart] = await Promise.all([contentfulData, cartData]); /* eslint-disable @typescript-eslint/no-unused-vars */ - const cartService = (await getApp()).get(CartService); + const cartService = await app.getCartService(); return ( <> diff --git a/apps/payments/next/app/lib/apiClient.ts b/apps/payments/next/app/_lib/apiClient.ts similarity index 100% rename from apps/payments/next/app/lib/apiClient.ts rename to apps/payments/next/app/_lib/apiClient.ts diff --git a/apps/payments/next/app/lib/stubs.ts b/apps/payments/next/app/_lib/stubs.ts similarity index 100% rename from apps/payments/next/app/lib/stubs.ts rename to apps/payments/next/app/_lib/stubs.ts diff --git a/apps/payments/next/app/nestapp/app.module.ts b/apps/payments/next/app/_nestapp/app.module.ts similarity index 82% rename from apps/payments/next/app/nestapp/app.module.ts rename to apps/payments/next/app/_nestapp/app.module.ts index 2d1359fbe61..3351b5f0471 100644 --- a/apps/payments/next/app/nestapp/app.module.ts +++ b/apps/payments/next/app/_nestapp/app.module.ts @@ -5,7 +5,7 @@ import { dotenvLoader, fileLoader, TypedConfigModule } from 'nest-typed-config'; import { CartManager, CartService } from '@fxa/payments/cart'; -import { AccountDatabaseFactory } from '@fxa/shared/db/mysql/account'; +import { AccountDatabaseNestFactory } from '@fxa/shared/db/mysql/account'; import { Module } from '@nestjs/common'; import { RootConfig } from './config'; @@ -21,6 +21,6 @@ import { RootConfig } from './config'; }), ], controllers: [], - providers: [AccountDatabaseFactory, CartService, CartManager], + providers: [AccountDatabaseNestFactory, CartService, CartManager], }) export class AppModule {} diff --git a/apps/payments/next/app/_nestapp/app.ts b/apps/payments/next/app/_nestapp/app.ts new file mode 100644 index 00000000000..ce7e3916ba9 --- /dev/null +++ b/apps/payments/next/app/_nestapp/app.ts @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import 'server-only'; + +import { CartService } from '@fxa/payments/cart'; +import { NestFactory } from '@nestjs/core'; + +import { AppModule } from './app.module'; + +class AppSingleton { + private app!: Awaited< + ReturnType + >; + + async getApp() { + if (this.app) return this.app; + this.app = await NestFactory.createApplicationContext(AppModule); + return this.app; + } + + async getCartService() { + return (await this.getApp()).get(CartService); + } +} + +export const app = new AppSingleton(); diff --git a/apps/payments/next/app/nestapp/config.ts b/apps/payments/next/app/_nestapp/config.ts similarity index 100% rename from apps/payments/next/app/nestapp/config.ts rename to apps/payments/next/app/_nestapp/config.ts diff --git a/apps/payments/next/app/nestapp/app.ts b/apps/payments/next/app/nestapp/app.ts deleted file mode 100644 index 88174404920..00000000000 --- a/apps/payments/next/app/nestapp/app.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import 'server-only'; - -import { NestFactory } from '@nestjs/core'; - -import { AppModule } from './app.module'; - -let app: Awaited>; - -export async function getApp() { - if (app) return app; - app = await NestFactory.createApplicationContext(AppModule); - return app; -} diff --git a/libs/shared/db/mysql/account/src/index.ts b/libs/shared/db/mysql/account/src/index.ts index 03c93395691..1e01e44154f 100644 --- a/libs/shared/db/mysql/account/src/index.ts +++ b/libs/shared/db/mysql/account/src/index.ts @@ -8,4 +8,4 @@ export { CartFactory } from './lib/factories'; export { setupAccountDatabase, AccountDbProvider } from './lib/setup'; export { testAccountDatabaseSetup } from './lib/tests'; export type { AccountDatabase } from './lib/setup'; -export { AccountDatabaseFactory } from './lib/account.provider'; +export { AccountDatabaseNestFactory } from './lib/account.provider'; diff --git a/libs/shared/db/mysql/account/src/lib/account.provider.ts b/libs/shared/db/mysql/account/src/lib/account.provider.ts index 25fc6247743..5e5a2f90e26 100644 --- a/libs/shared/db/mysql/account/src/lib/account.provider.ts +++ b/libs/shared/db/mysql/account/src/lib/account.provider.ts @@ -11,7 +11,7 @@ import { setupAccountDatabase, } from './setup'; -export const AccountDatabaseFactory: Provider = { +export const AccountDatabaseNestFactory: Provider = { provide: AccountDbProvider, useFactory: (mysqlConfig: MySQLConfig) => { return setupAccountDatabase(mysqlConfig);