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:
@@ -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,11 +42,14 @@ export default class RedisEventBusService extends AbstractEventBusModuleService
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Register our worker to handle emit calls
|
// Register our worker to handle emit calls
|
||||||
new Worker(moduleOptions.queueName ?? "events-queue", this.worker_, {
|
const shouldStartWorker = moduleDeclaration.worker_mode !== "server"
|
||||||
prefix: `${this.constructor.name}`,
|
if (shouldStartWorker) {
|
||||||
...(moduleOptions.workerOptions ?? {}),
|
new Worker(moduleOptions.queueName ?? "events-queue", this.worker_, {
|
||||||
connection: eventBusRedisConnection,
|
prefix: `${this.constructor.name}`,
|
||||||
})
|
...(moduleOptions.workerOptions ?? {}),
|
||||||
|
connection: eventBusRedisConnection,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -19,39 +19,52 @@ 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({
|
||||||
const serverActivity = Logger.activity(`Creating server`)
|
directory,
|
||||||
const server = GracefulShutdownServer.create(
|
expressApp: app,
|
||||||
app.listen(port, (err) => {
|
})
|
||||||
if (err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Logger.success(serverActivity, `Server is ready on port: ${port}`)
|
|
||||||
track("CLI_START_COMPLETED")
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handle graceful shutdown
|
const shouldStartServer =
|
||||||
const gracefulShutDown = () => {
|
configModule.projectConfig.worker_mode !== "worker"
|
||||||
server
|
|
||||||
.shutdown()
|
let server
|
||||||
.then(() => {
|
if (shouldStartServer) {
|
||||||
Logger.info("Gracefully stopping the server.")
|
const serverActivity = Logger.activity(`Creating server`)
|
||||||
process.exit(0)
|
server = GracefulShutdownServer.create(
|
||||||
})
|
app.listen(port, (err) => {
|
||||||
.catch((e) => {
|
if (err) {
|
||||||
Logger.error("Error received when shutting down the server.", e)
|
return
|
||||||
process.exit(1)
|
}
|
||||||
|
Logger.success(serverActivity, `Server is ready on port: ${port}`)
|
||||||
|
track("CLI_START_COMPLETED")
|
||||||
})
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handle graceful shutdown
|
||||||
|
const gracefulShutDown = () => {
|
||||||
|
server
|
||||||
|
.shutdown()
|
||||||
|
.then(() => {
|
||||||
|
Logger.info("Gracefully stopping the server.")
|
||||||
|
process.exit(0)
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
Logger.error("Error received when shutting down the server.", e)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on("SIGTERM", gracefulShutDown)
|
||||||
|
process.on("SIGINT", gracefulShutDown)
|
||||||
|
} else {
|
||||||
|
Logger.info("Running in worker mode, server will not be started.")
|
||||||
}
|
}
|
||||||
process.on("SIGTERM", gracefulShutDown)
|
|
||||||
process.on("SIGINT", gracefulShutDown)
|
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -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 ?? {},
|
||||||
|
|||||||
@@ -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,22 +107,33 @@ async function loadMedusaV2({
|
|||||||
container,
|
container,
|
||||||
})
|
})
|
||||||
|
|
||||||
await expressLoader({ app: expressApp, configModule })
|
if (shouldStartAPI) {
|
||||||
|
await expressLoader({ app: expressApp, configModule })
|
||||||
|
|
||||||
expressApp.use((req: Request, res: Response, next: NextFunction) => {
|
expressApp.use((req: Request, res: Response, next: NextFunction) => {
|
||||||
req.scope = container.createScope() as MedusaContainer
|
req.scope = container.createScope() as MedusaContainer
|
||||||
req.requestId = (req.headers["x-request-id"] as string) ?? v4()
|
req.requestId = (req.headers["x-request-id"] as string) ?? v4()
|
||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: Add Subscribers loader
|
// 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()
|
||||||
|
})
|
||||||
|
|
||||||
await apiLoader({
|
// TODO: Add Subscribers loader
|
||||||
container,
|
|
||||||
app: expressApp,
|
await apiLoader({
|
||||||
configModule,
|
container,
|
||||||
featureFlagRouter,
|
app: expressApp,
|
||||||
})
|
configModule,
|
||||||
|
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),
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
Reference in New Issue
Block a user