Engineering Design
Dependency Injection & IoC

Dependency Injection & IoC

How Medusa v2 Handles DI

Medusa v2 uses Awilix as its IoC container. Every module gets its own container. Services resolve dependencies from their module container via constructor injection. Cross-module operations are handled by Workflows — not by services calling each other directly.

Rule: DI within a module. Workflows across modules. Never call a service from another module directly inside a service method.

Pattern for Custom Services

// src/modules/downloads/service.ts
import { Logger } from "@medusajs/framework/types"
 
type InjectedDependencies = {
  logger: Logger
  // add other intra-module services here
}
 
export default class DownloadService {
  protected logger_: Logger
 
  constructor({ logger }: InjectedDependencies) {
    this.logger_ = logger
  }
}

Cross-Module Operations via Workflows

// src/workflows/create-download-links.ts
import { createWorkflow, createStep } from "@medusajs/framework/workflows-sdk"
 
const createLinksStep = createStep("create-links", async (input, { container }) => {
  const downloadService = container.resolve("downloadModuleService")
  const notificationService = container.resolve("notification")
  // cross-module: downloads + notifications both resolved here
})
 
export const createDownloadLinksWorkflow = createWorkflow(
  "create-download-links",
  (input) => { return createLinksStep(input) }
)

Circular Dependency Rule

If ServiceA resolves ServiceB, ServiceB must never resolve ServiceA. Circular deps crash at startup. Resolve upward (leaf services) or sideways (sibling services via workflow), never in a cycle.