feat: v2 - add worker mode (#6739)

**What**
- Adds support for starting a Medusa process with a worker mode.
- The worker modes supported are "shared", "worker", "server"
- In "worker" mode, API routes are not registered and modules that need to run workers (e.g., event bus redis) can use the flag to conditionally start workers.
- In "server" mode, API routes are registered and workers are not started.
- In "shared" mode, API routes are registered and workers are started. This is great for development.
This commit is contained in:
Sebastian Rindom authored and GitHub committed 2024-03-21 13:08:20 +00:00
1 parent 205573f5e3
commit 56481e683d
11 files changed
+101 -18

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
---
"@medusajs/medusa": patch
"@medusajs/event-bus-redis": patch
"@medusajs/modules-sdk": patch
"@medusajs/types": patch
---
feat: v2 - add worker mode
@@ -42,12 +42,15 @@ export default class RedisEventBusService extends AbstractEventBusModuleService
}) })
// Register our worker to handle emit calls // Register our worker to handle emit calls
const shouldStartWorker = moduleDeclaration.worker_mode !== "server"
if (shouldStartWorker) {
new Worker(moduleOptions.queueName ?? "events-queue", this.worker_, { new Worker(moduleOptions.queueName ?? "events-queue", this.worker_, {
prefix: `${this.constructor.name}`, prefix: `${this.constructor.name}`,
...(moduleOptions.workerOptions ?? {}), ...(moduleOptions.workerOptions ?? {}),
connection: eventBusRedisConnection, connection: eventBusRedisConnection,
}) })
} }
}
/** /**
* Emit a single event * Emit a single event
+16 -3
View File
@@ -19,9 +19,18 @@ export default async function ({ port, directory }) {
const app = express() const app = express()
try { try {
const { dbConnection } = await loaders({ directory, expressApp: app }) const { dbConnection, configModule, container } = await loaders({
directory,
expressApp: app,
})
const shouldStartServer =
configModule.projectConfig.worker_mode !== "worker"
let server
if (shouldStartServer) {
const serverActivity = Logger.activity(`Creating server`) const serverActivity = Logger.activity(`Creating server`)
const server = GracefulShutdownServer.create( server = GracefulShutdownServer.create(
app.listen(port, (err) => { app.listen(port, (err) => {
if (err) { if (err) {
return return
@@ -44,14 +53,18 @@ export default async function ({ port, directory }) {
process.exit(1) process.exit(1)
}) })
} }
process.on("SIGTERM", gracefulShutDown) process.on("SIGTERM", gracefulShutDown)
process.on("SIGINT", gracefulShutDown) process.on("SIGINT", gracefulShutDown)
} else {
Logger.info("Running in worker mode, server will not be started.")
}
scheduleJob(CRON_SCHEDULE, () => { scheduleJob(CRON_SCHEDULE, () => {
track("PING") track("PING")
}) })
return { dbConnection, server } return shouldStartServer ? { dbConnection, server } : { dbConnection }
} catch (err) { } catch (err) {
Logger.error("Error starting server", err) Logger.error("Error starting server", err)
process.exit(1) process.exit(1)
+14 -1
View File
@@ -1,4 +1,4 @@
import { getConfigFile } from "medusa-core-utils" import { getConfigFile, isDefined } from "medusa-core-utils"
import { ConfigModule } from "../types/global" import { ConfigModule } from "../types/global"
import logger from "./logger" import logger from "./logger"
@@ -58,11 +58,24 @@ export default (rootDirectory: string): ConfigModule => {
) )
} }
let worker_mode = configModule?.projectConfig?.worker_mode
if (!isDefined(worker_mode)) {
const env = process.env.MEDUSA_WORKER_MODE
if (isDefined(env)) {
if (env === "shared" || env === "worker" || env === "server") {
worker_mode = env
}
} else {
worker_mode = "shared"
}
}
return { return {
projectConfig: { projectConfig: {
jwt_secret: jwt_secret ?? "supersecret", jwt_secret: jwt_secret ?? "supersecret",
cookie_secret: cookie_secret ?? "supersecret", cookie_secret: cookie_secret ?? "supersecret",
...configModule?.projectConfig, ...configModule?.projectConfig,
worker_mode,
}, },
modules: configModule.modules ?? {}, modules: configModule.modules ?? {},
featureFlags: configModule?.featureFlags ?? {}, featureFlags: configModule?.featureFlags ?? {},
+17 -10
View File
@@ -1,9 +1,9 @@
import { createDefaultsWorkflow } from "@medusajs/core-flows" import { createDefaultsWorkflow } from "@medusajs/core-flows"
import { import {
InternalModuleDeclaration, InternalModuleDeclaration,
ModulesDefinition ModulesDefinition,
} from "@medusajs/modules-sdk" } from "@medusajs/modules-sdk"
import { MODULE_RESOURCE_TYPE } from "@medusajs/types" import { ConfigModule, MODULE_RESOURCE_TYPE } from "@medusajs/types"
import { import {
ContainerRegistrationKeys, ContainerRegistrationKeys,
MedusaV2Flag, MedusaV2Flag,
@@ -91,14 +91,7 @@ async function loadMedusaV2({
}) { }) {
const container = createMedusaContainer() const container = createMedusaContainer()
// Add additional information to context of request const shouldStartAPI = configModule.projectConfig.worker_mode !== "worker"
expressApp.use((req: Request, res: Response, next: NextFunction) => {
const ipAddress = requestIp.getClientIp(req) as string
;(req as any).request_context = {
ip_address: ipAddress,
}
next()
})
const pgConnection = await pgConnectionLoader({ container, configModule }) const pgConnection = await pgConnectionLoader({ container, configModule })
@@ -114,6 +107,7 @@ async function loadMedusaV2({
container, container,
}) })
if (shouldStartAPI) {
await expressLoader({ app: expressApp, configModule }) await expressLoader({ app: expressApp, configModule })
expressApp.use((req: Request, res: Response, next: NextFunction) => { expressApp.use((req: Request, res: Response, next: NextFunction) => {
@@ -122,6 +116,15 @@ async function loadMedusaV2({
next() next()
}) })
// Add additional information to context of request
expressApp.use((req: Request, res: Response, next: NextFunction) => {
const ipAddress = requestIp.getClientIp(req) as string
;(req as any).request_context = {
ip_address: ipAddress,
}
next()
})
// TODO: Add Subscribers loader // TODO: Add Subscribers loader
await apiLoader({ await apiLoader({
@@ -130,6 +133,7 @@ async function loadMedusaV2({
configModule, configModule,
featureFlagRouter, featureFlagRouter,
}) })
}
await medusaProjectApisLoader({ await medusaProjectApisLoader({
rootDirectory, rootDirectory,
@@ -142,6 +146,7 @@ async function loadMedusaV2({
await createDefaultsWorkflow(container).run() await createDefaultsWorkflow(container).run()
return { return {
configModule,
container, container,
app: expressApp, app: expressApp,
pgConnection, pgConnection,
@@ -153,6 +158,7 @@ export default async ({
expressApp, expressApp,
isTest, isTest,
}: Options): Promise<{ }: Options): Promise<{
configModule: ConfigModule
container: MedusaContainer container: MedusaContainer
dbConnection?: Connection dbConnection?: Connection
app: Express app: Express
@@ -319,6 +325,7 @@ export default async ({
track("SEARCH_ENGINE_INDEXING_COMPLETED", { duration: searchAct.duration }) track("SEARCH_ENGINE_INDEXING_COMPLETED", { duration: searchAct.duration })
return { return {
configModule,
container, container,
dbConnection, dbConnection,
app: expressApp, app: expressApp,
@@ -41,9 +41,19 @@ export default async ({
}: Options): Promise<void> => { }: Options): Promise<void> => {
const resolved = getResolvedPlugins(rootDirectory, configModule) || [] const resolved = getResolvedPlugins(rootDirectory, configModule) || []
const shouldStartAPI = configModule.projectConfig.worker_mode !== "worker"
await promiseAll( await promiseAll(
resolved.map(async (pluginDetails) => { resolved.map(async (pluginDetails) => {
await registerApi(pluginDetails, app, container, configModule, activityId) if (shouldStartAPI) {
await registerApi(
pluginDetails,
app,
container,
configModule,
activityId
)
}
await registerSubscribers(pluginDetails, container, activityId) await registerSubscribers(pluginDetails, container, activityId)
await registerWorkflows(pluginDetails) await registerWorkflows(pluginDetails)
}) })
@@ -168,6 +168,7 @@ export const loadMedusaApp = async (
} }
const medusaApp = await MedusaApp({ const medusaApp = await MedusaApp({
workerMode: configModule.projectConfig.worker_mode,
modulesConfig: configModules, modulesConfig: configModules,
servicesConfig: joinerConfig, servicesConfig: joinerConfig,
remoteFetchData: remoteQueryFetchData(container), remoteFetchData: remoteQueryFetchData(container),
+7 -2
View File
@@ -71,7 +71,8 @@ export async function loadModules(
modulesConfig, modulesConfig,
sharedContainer, sharedContainer,
migrationOnly = false, migrationOnly = false,
loaderOnly = false loaderOnly = false,
workerMode: "shared" | "worker" | "server" = "server"
) { ) {
const allModules = {} const allModules = {}
@@ -113,6 +114,7 @@ export async function loadModules(
moduleExports, moduleExports,
migrationOnly, migrationOnly,
loaderOnly, loaderOnly,
workerMode,
})) as LoadedModule })) as LoadedModule
if (loaderOnly) { if (loaderOnly) {
@@ -202,6 +204,7 @@ export type MedusaAppOutput = {
} }
export type MedusaAppOptions = { export type MedusaAppOptions = {
workerMode?: "shared" | "worker" | "server"
sharedContainer?: MedusaContainer sharedContainer?: MedusaContainer
sharedResourcesConfig?: SharedResources sharedResourcesConfig?: SharedResources
loadedModules?: LoadedModule[] loadedModules?: LoadedModule[]
@@ -232,6 +235,7 @@ async function MedusaApp_({
onApplicationStartCb, onApplicationStartCb,
migrationOnly = false, migrationOnly = false,
loaderOnly = false, loaderOnly = false,
workerMode = "server",
}: MedusaAppOptions & { migrationOnly?: boolean } = {}): Promise<{ }: MedusaAppOptions & { migrationOnly?: boolean } = {}): Promise<{
modules: Record<string, LoadedModule | LoadedModule[]> modules: Record<string, LoadedModule | LoadedModule[]>
link: RemoteLink | undefined link: RemoteLink | undefined
@@ -300,7 +304,8 @@ async function MedusaApp_({
modules, modules,
sharedContainer_, sharedContainer_,
migrationOnly, migrationOnly,
loaderOnly loaderOnly,
workerMode
) )
if (loaderOnly) { if (loaderOnly) {
+4 -1
View File
@@ -69,6 +69,7 @@ export type ModuleBootstrapOptions = {
* Forces the modules bootstrapper to only run the modules loaders and return prematurely * Forces the modules bootstrapper to only run the modules loaders and return prematurely
*/ */
loaderOnly?: boolean loaderOnly?: boolean
workerMode?: "shared" | "worker" | "server"
} }
export type LinkModuleBootstrapOptions = { export type LinkModuleBootstrapOptions = {
@@ -225,6 +226,7 @@ export class MedusaModule {
injectedDependencies, injectedDependencies,
migrationOnly, migrationOnly,
loaderOnly, loaderOnly,
workerMode,
}: ModuleBootstrapOptions): Promise<{ }: ModuleBootstrapOptions): Promise<{
[key: string]: T [key: string]: T
}> { }> {
@@ -267,6 +269,7 @@ export class MedusaModule {
options: declaration?.options ?? declaration, options: declaration?.options ?? declaration,
alias: declaration?.alias, alias: declaration?.alias,
main: declaration?.main, main: declaration?.main,
worker_mode: workerMode,
} }
} }
@@ -302,7 +305,7 @@ export class MedusaModule {
moduleResolutions, moduleResolutions,
logger: logger_, logger: logger_,
migrationOnly, migrationOnly,
loaderOnly loaderOnly,
}) })
} catch (err) { } catch (err) {
errorLoading(err) errorLoading(err)
@@ -485,6 +485,25 @@ export type ProjectConfigOptions = {
* ``` * ```
*/ */
jobs_batch_size?: number jobs_batch_size?: number
/**
* Configure the application's worker mode. The default value is `shared`.
* - Use `shared` if you want to run the application in a single process.
* - Use `worker` if you want to run the a worker process only.
* - Use `server` if you want to run the application server only.
*
* @example
* ```js title="medusa-config.js"
* module.exports = {
* projectConfig: {
* worker_mode: "shared"
* // ...
* },
* // ...
* }
* ```
*/
worker_mode?: "shared" | "worker" | "server"
} }
/** /**
+1
View File
@@ -48,6 +48,7 @@ export type InternalModuleDeclaration = {
* If the module is the main module for the key when multiple ones are registered * If the module is the main module for the key when multiple ones are registered
*/ */
main?: boolean main?: boolean
worker_mode?: "shared" | "worker" | "server"
} }
export type ExternalModuleDeclaration = { export type ExternalModuleDeclaration = {