Medusa Transaction Management for API Operations: How the Decorator Pattern Ensures Data Consistency
Medusa guarantees ACID compliance for all API mutations by automatically wrapping service-layer database operations in transactions using the @InjectTransactionManager decorator and a shared context propagation mechanism.
The Medusa open-source commerce framework provides robust transaction management capabilities that ensure data consistency across API operations. By leveraging the @InjectTransactionManager decorator and a sharedContext propagation pattern, Medusa automatically wraps every write operation in an atomic database transaction. This architecture prevents partial data writes and maintains referential integrity throughout the request lifecycle.
Core Transaction Management Architecture
The @InjectTransactionManager Decorator
At the heart of Medusa's transaction management strategy lies the @InjectTransactionManager decorator. When applied to a service method, this decorator intercepts the call to extract a transaction manager from the sharedContext parameter. If no transaction exists, it initializes a new one automatically. The decorator then injects the manager as the first argument (typically named manager) to the service method, ensuring all subsequent ORM calls execute within the same transactional boundary.
According to the Medusa source code, this pattern appears consistently across virtually every module. In packages/modules/store/src/services/store-module-service.ts (lines 82, 121, 194), the decorator wraps mutation methods to guarantee atomic updates to store configurations and related entities.
SharedContext Propagation Pattern
The sharedContext (or Context) object serves as the transaction carrier that propagates from API controllers through to service layers. When an API route receives a request, Medusa initializes a context containing a reference to the current transaction manager. This context passes through method signatures as an optional parameter (typically sharedContext?: Context), allowing downstream services to participate in the same database transaction without explicit transaction handling code.
In packages/modules/product/src/services/product-module-service.ts (lines 367-424), service methods accept this shared context, enabling complex product updates—including variants, options, and inventory associations—to commit or rollback as a single atomic unit.
How Request-Level Transactions Work
Medusa implements request-level transaction scoping to ensure complete operation isolation:
-
Context Initialization – When an API endpoint receives a mutation request, the framework creates a
Contextobject containing a fresh transactionalEntityManager(or Knex equivalent). -
Propagation Chain – The controller passes this context to service methods via the
sharedContextparameter, creating a chain of operations that share the same transactional scope. -
Decorated Execution – Service methods marked with
@InjectTransactionManagerreceive the transaction manager automatically, as seen inpackages/modules/promotion/src/services/promotion-module.ts, where complex promotional rule updates require multi-table consistency. -
Atomic Commit – Upon successful completion of all operations, the transaction commits automatically. If any operation fails, the entire transaction rolls back, leaving the database unchanged.
Automatic Rollback and Error Handling
Medusa's transaction management provides automatic rollback protection without requiring explicit try-catch blocks in business logic. When an exception bubbles out of a method decorated with @InjectTransactionManager, the underlying Data Access Layer (DAL) catches the error and triggers a full transaction rollback. This behavior mirrors standard ORM transaction scopes while abstracting the complexity from service implementations.
The rollback mechanism ensures that partially completed operations—such as creating a product but failing to associate its variants—never leave the database in an inconsistent state. All related tables revert to their pre-operation condition automatically.
Advanced Patterns: Manual Transaction Sharing
For complex business logic spanning multiple services, Medusa supports explicit transaction sharing through manual transaction initialization.
Advanced callers can initiate transactions via the repository's manager.transaction API or the TransactionManager.startTransaction() utility, then forward the resulting transaction manager through the sharedContext to subsequent service calls. This approach allows multiple discrete service methods to participate in a single atomic operation, maintaining consistency across module boundaries.
Distributed Transactions with the Workflow Engine
For long-running or multi-step operations that extend beyond a single request lifecycle, Medusa employs the workflow engine (specifically workflow-engine-redis) to provide distributed transaction capabilities.
As defined in packages/modules/workflow-engine-redis/src/utils/workflow-orchestrator-storage.ts, the engine implements DistributedTransactionType with checkpoint handling and compensation logic. This system records transaction progress at each workflow step, enabling rollback of partially completed distributed operations if subsequent steps fail. The workflow engine effectively extends ACID guarantees to complex business processes that span multiple services, external APIs, or time-delayed operations.
Practical Implementation Examples
Basic API Controller with Automatic Transactions
import { ProductModuleService } from "@medusajs/modules-product"
class MyController {
constructor(private readonly productService: ProductModuleService) {}
async updateProduct(productId: string, data: UpdateProductDTO) {
// The service method is decorated with @InjectTransactionManager,
// so any DB writes are wrapped in a single transaction.
await this.productService.update(productId, data, {
// Passing the empty Context tells Medusa to start a new transaction.
sharedContext: {}
})
}
}
Manual Transaction Coordination Across Services
import { TransactionManager } from "@medusajs/framework/utils"
import { ProductModuleService } from "@medusajs/modules-product"
import { InventoryModuleService } from "@medusajs/modules-inventory"
async function createProductWithInventory(
productData: CreateProductDTO,
inventoryData: CreateInventoryDTO
) {
// Open a transaction manually
const transaction = await TransactionManager.startTransaction()
const ctx: Context = { transactionManager: transaction }
// All service calls receive the same `sharedContext`
await productService.create(productData, { sharedContext: ctx })
await inventoryService.create(inventoryData, { sharedContext: ctx })
// Commit the transaction once all steps succeed
await transaction.commit()
}
Workflow Step with Compensation Support
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
export const deleteProductStep = createStep(
"delete-product",
async (ids: string[], { container }) => {
const productService = container.resolve("productModuleService")
// The service method below is decorated with @InjectTransactionManager
await productService.softDelete(ids, {})
return new StepResponse(void 0, ids)
},
// Compensation step – restores products if the workflow rolls back
async (idsToRestore, { container }) => {
if (!idsToRestore?.length) return
const productService = container.resolve("productModuleService")
await productService.restore(idsToRestore, {})
}
)
Summary
- Medusa implements transaction management through the
@InjectTransactionManagerdecorator, which automatically injects transactional managers into service methods. - The
sharedContextobject propagates transaction state from API controllers through service layers, ensuring all operations within a request share the same transactional scope. - Automatic rollback occurs when exceptions bubble out of decorated methods, preventing partial database updates without requiring explicit error handling.
- Manual transaction sharing allows advanced use cases to coordinate atomic operations across multiple services using
TransactionManager.startTransaction(). - The workflow engine extends transaction guarantees to distributed, long-running processes through checkpoint tracking and compensation logic as implemented in
packages/modules/workflow-engine-redis/src/utils/workflow-orchestrator-storage.ts. - Real-world implementations in
packages/modules/product/src/services/product-module-service.tsandpackages/modules/store/src/services/store-module-service.tsdemonstrate consistent application of these patterns across the codebase.
Frequently Asked Questions
What is the @InjectTransactionManager decorator in Medusa?
The @InjectTransactionManager decorator is a core framework utility that intercepts service method calls to inject a transactional database manager as the first argument. It extracts the transaction manager from the sharedContext parameter or creates a new transaction if none exists, ensuring all database operations within the method execute atomically. This pattern appears throughout Medusa's module services, including packages/modules/product/src/services/product-module-service.ts and the store module service.
How does Medusa handle transaction rollback when an API operation fails?
Medusa handles rollbacks automatically through its Data Access Layer when exceptions propagate out of methods decorated with @InjectTransactionManager. If any database operation fails within a transactional method, the framework catches the exception and triggers a complete rollback of all changes made within that transaction scope. This guarantees that the database remains consistent without requiring explicit rollback commands in business logic.
Can I share transactions across multiple service calls in Medusa?
Yes, Medusa supports explicit transaction sharing across multiple service calls by manually initializing a transaction via TransactionManager.startTransaction() and passing the resulting manager through the sharedContext parameter to subsequent service methods. This approach ensures that operations spanning multiple services—such as creating a product and simultaneously updating inventory levels—commit or rollback as a single atomic unit.
Does Medusa support distributed transactions for long-running workflows?
Yes, Medusa supports distributed transactions through its workflow engine (specifically workflow-engine-redis), which implements checkpoint persistence and compensation patterns defined in packages/modules/workflow-engine-redis/src/utils/workflow-orchestrator-storage.ts. The engine tracks transaction state across workflow steps and can execute compensation functions to rollback completed steps if later operations fail, providing ACID-like guarantees for complex, multi-step business processes that extend beyond single request boundaries.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →